あるスライスからインデックス指定で新しいスライスを作ってから、appendで要素を追加した場合、
新しいスライスにしか影響を与えないと思っていたのだが、必ずしもそうではないらしい。
以下はversion 1.19.2で検証した結果である。
package main
import "fmt"
func main() {
nums1 := []int{1, 2, 3, 4, 5}
s1 := nums1[:4]
fmt.Printf("len = %d, cap = %d\n", len(s1), cap(s1))
s1 = append(s1, 6) // (1)
s1[0] = 10 // (2)
s1 = append(s1, 7) // (3)
s1[1] = 20 // (4)
fmt.Printf("nums1 = %v\n", nums1)
fmt.Printf("s1 = %v\n", s1)
}
# 出力結果
len = 4, cap = 5
nums1 = [10 2 3 4 6]
s1 = [10 20 3 4 6 7]
nums1から最後の要素のみ削除したs1を作成すると、len < capとなっている。
len < cap の状態で(1)のようにs1に一度appendすると元のnums1の5番目の要素も変わってしまう。
(1)の時点ではs1のcapacityに余裕があるため、新しい配列の領域を確保せず、
末尾の後ろに新しい要素を追加してlenを1増やすだけになる。
s1の末尾の後ろはnums1の5番目の要素なので、こちらもs1にappendした値に変わってしまう。
(2)の時点では、まだnums1とs1が同じ配列を参照しているため、s1の変更がnums1にも影響する。
(3)のappendでcapacityを超過するので、新しい配列の領域が確保されてs1は新しい配列を参照するようになる。
この時点で、元のスライスとの関係が切れるので、(4)のようにs1の要素を変更してもnums1には影響がない。
スライスからスライスを作ったときのlenとcapがどう決まるかははっきりとは分かっていないが、
インデックス指定を適当に変えて見た感じでは、len < capが一致しないケースが多く見られた。
個人的にはスライス作成時に len = cap となるのが正しい気がするので、今後のバージョンアップを期待したい。
追記 (2023/01/14)
@nobonobo さんからコメントを頂いたのを受けて追記。
len = cap となるのが正しい気がするので、今後のバージョンアップを期待したい。
と書いたが、 len = cap に必ずしもならないのは言語仕様とのことで、バージョンアップで変わる可能性は低そうだ。
len = capを意図的に実現したい場合は、フルスライス式 (slice[low:high:max] のように値を3つ指定するもの) で len = max を指定する。
この記事の例だと以下のようになる。
s1 := nums1[:4:4]
真ん中の4がhighで、右の4がmaxを表す。
フルスライス式のcapacityはmax - low
で決まる。
よって、capacity = 4 - 0 = 4
となり、len = capacity
が実現する。
(参考) The Go Programming Language Specification#Slice expressions
ちなみにmaxを指定しない通常の(?)スライス式のcapacityがどう決まるかは、言語仕様に記載がないが、 (見落としであれば教えて欲しい)
capの値をmaxとして計算しているようである。
以下はそれを確認したコード
package main
import "fmt"
func main() {
nums1 := make([]int, 10, 20)
for i, _ := range nums1 {
nums1[i] = i + 1
}
fmt.Printf("len = %d, cap = %d\n", len(nums1), cap(nums1))
s1 := nums1[2:4]
fmt.Printf("len = %d, cap = %d\n", len(s1), cap(s1))
}
# 出力結果
len = 10, cap = 20
len = 2, cap = 18