Goでsliceの一部を抽出する時の注意点
sliceの構造
Goのsliceは 配列へのポインタ
長さ
容量
を持った構造体として定義されています。
type slice struct {
array unsafe.Pointer
len int
cap int
}
sliceの構造については以下の記事が分かりやすかったです。
この構造をきちんと理解していなくてバグを踏んだので、その時調べたことをまとめました。
sliceの要素を抽出
sliceの要素の一部を抜き出したい時は以下のように書きます。
base := []int{1, 2, 3, 4}
newSlice := base[1:2]
この時の注意点として、元のsliceと新しく生成したsliceは同じ配列へのポインタを持っています。
例えば以下のようにsliceの一部を取り出し、別のsliceを作り、その中身を出力してみます。
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
base := []int{1, 2, 3, 4}
from1to2 := base[1:2]
to2 := base[:2]
from1 := base[1:]
from2 := base[2:]
all := base[:]
empty := base[:0]
appended := []int{}
appended = append(appended, base...)
printSliceHeader("base ", base)
printSliceHeader("from1to2", from1to2)
printSliceHeader("to2 ", to2)
printSliceHeader("from1 ", from1)
printSliceHeader("from2 ", from2)
printSliceHeader("all ", all)
printSliceHeader("empty ", empty)
printSliceHeader("appended", appended)
}
func printSliceHeader(n string, ss []int) {
ptr := unsafe.Pointer(&ss)
s := (*reflect.SliceHeader)(ptr)
fmt.Printf("%s : %#v\n", n, s)
}
実行すると以下のようになります。
base : &reflect.SliceHeader{Data:0xc000134000, Len:4, Cap:4}
from1to2 : &reflect.SliceHeader{Data:0xc000134008, Len:1, Cap:3}
to2 : &reflect.SliceHeader{Data:0xc000134000, Len:2, Cap:4}
from1 : &reflect.SliceHeader{Data:0xc000134008, Len:3, Cap:3}
from2 : &reflect.SliceHeader{Data:0xc000134010, Len:2, Cap:2}
all : &reflect.SliceHeader{Data:0xc000134000, Len:4, Cap:4}
empty : &reflect.SliceHeader{Data:0xc000134000, Len:0, Cap:4}
appended : &reflect.SliceHeader{Data:0xc000134020, Len:4, Cap:4}
base, all, empty と from1to2, from1 はそれぞれ同じポインタを指しています。
要するに、最初にbaseを生成した時に配列aが作られ、all, empty は配列aの0番目を指し、from1to2, from1 は配列aの1番目を指し、from2 は配列aの2番目を指している、ということです。
appendedは別のsliceとして生成したので、全く別の配列を指しています。
そのため、一つのsliceの要素を変更すると全てのsliceに影響があります。
base[1] = 9
fmt.Println("base :", base)
fmt.Println("from1to2 :", from1to2)
fmt.Println("to2 :", to2)
fmt.Println("from1 :", from1)
fmt.Println("from2 :", from2)
fmt.Println("all :", all)
fmt.Println("empty :", empty)
fmt.Println("appended :", appended)
base : [1 9 3 4]
from1to2 : [9]
to2 : [1 9]
from1 : [9 3 4]
from2 : [3 4]
all : [1 9 3 4]
empty : []
appended : [1 2 3 4]
意図せずsliceの値が変わってしまう可能性があるので気をつけたほうがよさそうです。
sliceの一部を抽出した時にできるslice
これまでの結果から、sliceを抽出した場合、長さは指定した添え字分の長さになっていそうです。
容量については、以下のように長さと容量の違うsliceを抽出してみると、
base := make([]int, 0, 4)
base = append(base, 1, 2, 3)
from1to2 := base[1:2]
printSliceHeader("base ", base)
printSliceHeader("from1to2", from1to2)
以下のようになります。
base : &reflect.SliceHeader{Data:0xc00012a000, Len:3, Cap:4}
from1to2 : &reflect.SliceHeader{Data:0xc00012a008, Len:1, Cap:3}
容量は元のsliceから開始地点の添え字分を引いたものになってそうです。
以上より、長さlen
容量cap
配列へのポインタptr
のsliceに対してslice[from:to]
を実行すると、長さto-from
容量cap-from
配列へのポインタptr+from
のsliceができるようです。
より詳しいことは以下に書いてあります。
appendして配列を更新した場合
sliceのcapを超えてappendするとsliceの指す配列自体が生成し直されるので、他のsliceに影響しなくなります。
printSliceHeader("base ", base)
base = append(base, 5)
base[1] = 9
printSliceHeader("base ", base)
printSliceHeader("from1to2", from1to2)
printSliceHeader("to2 ", to2)
printSliceHeader("from1 ", from1)
printSliceHeader("from2 ", from2)
printSliceHeader("all ", all)
printSliceHeader("empty ", empty)
printSliceHeader("appended", appended)
fmt.Println("base :", base)
fmt.Println("from1to2 :", from1to2)
fmt.Println("to2 :", to2)
fmt.Println("from1 :", from1)
fmt.Println("from2 :", from2)
fmt.Println("all :", all)
fmt.Println("empty :", empty)
fmt.Println("appended :", appended)
base : &reflect.SliceHeader{Data:0xc0000ba000, Len:4, Cap:4}
base : &reflect.SliceHeader{Data:0xc0000c2040, Len:5, Cap:8}
from1to2 : &reflect.SliceHeader{Data:0xc0000ba008, Len:1, Cap:3}
to2 : &reflect.SliceHeader{Data:0xc0000ba000, Len:2, Cap:4}
from1 : &reflect.SliceHeader{Data:0xc0000ba008, Len:3, Cap:3}
from2 : &reflect.SliceHeader{Data:0xc0000ba010, Len:2, Cap:2}
all : &reflect.SliceHeader{Data:0xc0000ba000, Len:4, Cap:4}
empty : &reflect.SliceHeader{Data:0xc0000ba000, Len:0, Cap:4}
appended : &reflect.SliceHeader{Data:0xc0000ba020, Len:4, Cap:4}
base : [1 9 3 4 5]
from1to2 : [2]
to2 : [1 2]
from1 : [2 3 4]
from2 : [3 4]
all : [1 2 3 4]
empty : []
appended : [1 2 3 4]