Edited at

[Go]スライスの複製に便利な「copy」関数のサンプルと、直接代入した場合の罠


とりあえずサンプル

スライスsを、スライスtにコピーしたいとする。


Gocopu関数のサンプル

package main

import "fmt"

func main() {
s := []int{0, 10, 20, 30} // => [0 10 20 30] ←スライスsを作成

t := make([]int, len(s)) // スライスtの骨組みを作成する
fmt.Println(t) // => [0 0 0 0] ←まだ値には0が入っている

n := copy(t, s) // スライスsをスライスtにコピーする

fmt.Println(t) // => [0 10 20 30] ←値がスライスsと同じ値になった
fmt.Println(n) // => 4 ←コピーされた要素の個数が返されている
}



どうして、t := sとしないのか


下記のように「copy関数」を使わずに代入するのはダメ?


Go:単純にコピーしてみた例

package main

import "fmt"

func main() {
s := []int{0, 10, 20, 30}

t := s

fmt.Println(s) // => [0, 10, 20, 30]
fmt.Println(t) // => [0, 10, 20, 30] ←コピーできてるくない?
}


上のコードのように、シンプルに代入したら同じスライスができているのに、これではダメなのか…?

結論から言うと、これダメです。


上のコードに、もう少し追記すれば分かるかと思います。


Go参照型の罠

package main

import "fmt"

func main() {
s := []int{0, 10, 20, 30}

t := s
t[0] = 100 // 1番目の値を0から100に書き換える

fmt.Println(t) // [100 10 20 30] ←1番目の値は書き換えられているので100
fmt.Println(s) // [100 10 20 30] ←あれ? スライスsは書き換えていないのに…?
}


このように、コピー先のスライスを編集すると、コピー元のスライスにまで反映されてしまいます。

これは、Goのスライスが「参照型」なためとのことです。

Goでスライスを使う場合には、慣れるまでは、この参照型によってバグが発生するといったことも出てきそうです。。

2019/08/13 追記)

コメントでご指摘頂いており、上の記述は間違っています。修正は以下の通りです。

stに代入コピーする段階で、内部的にsのポインタまでコピーしてしまうことが起こっています。

これにより、tsと同じメモリ領域の配列(つまり、同じ配列)を参照している状態になってしまうため、どちらか一方を変更したら両方とも変更されてしまう…といったことが起こるようです。

これに対して、copy関数を使った場合は、新しいメモリ領域に新しい配列を作成して、全く別の配列を参照する形になります。これによって、別個の配列として扱えるそうです。

詳しくは、この記事のコメント欄で@c-yanさんが説明して下さっていますので、ぜひ見て頂けると分かりやすいかと思います。