🥏

【Go】スライスやマップのコピーについて

2023/01/17に公開

はじめに

https://github.com/uber-go/guide/blob/master/style.md#copy-slices-and-maps-at-boundaries

Slices and maps contain pointers to the underlying data so be wary of scenarios when they need to be copied.

こちらのドキュメントのCopy Slices and Maps at Boundariesに記載されているように、Goでスライスやマップをコピーするときには、注意が必要です。

ドキュメントにはGoodとBadのサンプルが記載されていますので、それぞれどのような挙動になるか、コードを動かして確認します。


挙動を確認する

引数として渡した元データの変更が、コピー先に影響してしまう例

ドキュメントのBadの例を参考に、実行可能なコードを用意しました。

package main

import "fmt"

type Trip struct {
	Name string
}

type Driver struct {
	trips []Trip
}

func (d *Driver) SetTrips(trips []Trip) {
	d.trips = trips
}

func main() {

	trips := []Trip{
		{Name: "A"},
		{Name: "B"},
		{Name: "C"},
	}

	d1 := &Driver{}
	d1.SetTrips(trips) // (1)

	fmt.Println("Before d1: ", d1)
	fmt.Println("Before trips: ", trips)

	trips[0] = Trip{Name: "Changed"} // (2)

	fmt.Println("After d1: ", d1)
	fmt.Println("After trips: ", trips)
}

このコードを実行すると、つぎのような結果が得られます。

Before d1:  &{[{A} {B} {C}]}
Before trips:  [{A} {B} {C}]
After d1:  &{[{Changed} {B} {C}]}
After trips:  [{Changed} {B} {C}]

(1)でtripsというスライスをd1.SetTrips()に渡した後に、(2)でtripsの0番目の要素を変更しています。
その後にd1tripsの変数を出力していますが、tripsの0番目の要素の変更が、d1にも影響してしまっています。

引数として渡した元データの変更が、コピー先に影響しない例

つぎにGoodの例を参考に、実行可能なコードを用意しました。

package main

import "fmt"

type Trip struct {
	Name string
}

type Driver struct {
	trips []Trip
}

func (d *Driver) SetTrips(trips []Trip) {
	d.trips = make([]Trip, len(trips)) // (3)
	copy(d.trips, trips)
}

func main() {

	trips := []Trip{
		{Name: "A"},
		{Name: "B"},
		{Name: "C"},
	}

	d1 := &Driver{}
	d1.SetTrips(trips)

	fmt.Println("Before d1: ", d1)
	fmt.Println("Before trips: ", trips)

	trips[0] = Trip{Name: "Changed"}

	fmt.Println("After d1: ", d1)
	fmt.Println("After trips: ", trips)
}

Badの例との違いは、SetTrips()の引数のtripsをcopy()で設定していることです(3)。

このコードを実行した結果はつぎのようになりました。

Before d1:  &{[{A} {B} {C}]}
Before trips:  [{A} {B} {C}]
After d1:  &{[{A} {B} {C}]}
After trips:  [{Changed} {B} {C}]

copy()を利用したことで、tripsd1.tripsが同じポインタを参照しないようになり、tripsへの変更がd1に影響しないようになりました。

Discussion