スライスが元の配列を共有していて予期せぬ書き換えが起きる
スライスを別のスライスから切り出すと、多くの場合は同じ配列を参照したままになります。一方を書き換えると、共有している範囲がもう一方にも影響します。
なぜエラーが出ないのか
出力: [1 99 3 4 5]
(エラーなし)- Goは何も報告しません。コンパイルも実行も正常に終わるコードだからです
出力: [1 99 3 4 5]- 実際の出力 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 package main 2 3 import "fmt" 4 5 func main() { 6 original := []int{1, 2, 3, 4, 5} 7 sub := original[1:3] ^ 8 sub[0] = 99 9 fmt.Println(original) 10 }
original[1:3]は新しい配列を作らず元の配列を参照するため、subへの書き込みがoriginalにも反映されてしまいます。
直し方: sub := original[1:3] を sub := append([]int{}, original[1:3]...) にします。
パターン2
1 package main 2 3 import "fmt" 4 5 func main() { 6 original := []int{10, 20, 30, 40} 7 sub := original[0:2] ^ 8 sub[0] = 99 9 fmt.Println(original) 10 }
独立したコピーが欲しい場合は、append([]int{}, ...)やcopy()で明示的に複製する必要があります。
直し方: sub := original[0:2] を sub := append([]int{}, original[0:2]...) にします。
パターン3
1 package main 2 3 import "fmt" 4 5 func main() { 6 original := []int{5, 6, 7, 8, 9} 7 sub := original[2:4] ^ 8 sub[0] = 99 9 fmt.Println(original) 10 }
サブスライスの3番目の要素(添字2)を書き換えたつもりが、元のoriginalの該当位置も変わってしまいます。
直し方: sub := original[2:4] を sub := append([]int{}, original[2:4]...) にします。
パターン4
1 package main 2 3 import "fmt" 4 5 func main() { 6 original := []int{1, 1, 1, 1} 7 sub := original[1:3] ^ 8 sub[0] = 99 9 fmt.Println(original) 10 }
全要素が同じ値でも、スライス演算はコピーを作らないという性質は変わりません。
直し方: sub := original[1:3] を sub := append([]int{}, original[1:3]...) にします。
パターン5
1 package main 2 3 import "fmt" 4 5 func main() { 6 original := []int{2, 4, 6, 8, 10} 7 sub := original[3:5] ^ 8 sub[0] = 99 9 fmt.Println(original) 10 }
末尾側の範囲を切り出した場合でも、共有の性質は同じで、書き込みは相互に影響します。
直し方: sub := original[3:5] を sub := append([]int{}, original[3:5]...) にします。
よくある誤解
「スライスを切り出せば、元とは独立した新しいコピーになるはず」という思い込みは誤りです。スライス式は多くの場合コピーを作らず同じ配列を参照するため、独立させたいならcopy()で明示的に複製する必要があります。
まとめ
スライスが元の配列を共有していて予期せぬ書き換えが起きるは中級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。