0
0

More than 1 year has passed since last update.

Goのスライスの中身がすべて同じ値になる

Posted at

結論

range の左辺はただの変数である。
繰り返しのたびに再代入しているだけなので、気をつける。

現象

上手い例がかけないが、例えば以下の状況


type Foo struct {
  Item string
}

foo1 := Foo{"1"}
foo2 := Foo{"2"}
foo3 := Foo{"3"}
slice := []Foo{foo1, foo2, foo3}

var foos []*Foo
for _, foo := range slice {
  foos = append(foos, &foo)
}

for _, foo := range foos {
  fmt.Println(foo.Item)
}

// => 3
// => 3
// => 3

// 中身が同じ、なぜ?

なぜか中身がすべておなじになる。

原因

range の第二引数のポインタを格納してしまっているため。

詳しくは foos の中身をプリントしてみるとわかる


type Foo struct {
  Item string
}

foo1 := Foo{"1"}
foo2 := Foo{"2"}
foo3 := Foo{"3"}
slice := []Foo{foo1, foo2, foo3}

var foos []*Foo
for _, foo := range slice {
  foos = append(foos, &foo)
}


fmt.Println(foos)

// => [0x14000010230 0x14000010230 0x14000010230]

同じポインタが3つ格納されている。
これはどこで格納されたかと言うと、 繰り返し処理のときに range の左辺の変数のポインタが格納されている。


type Foo struct {
  Item string
}

foo1 := Foo{"1"}
foo2 := Foo{"2"}
foo3 := Foo{"3"}
slice := []Foo{foo1, foo2, foo3}

var foos []*Foo
for _, foo := range slice {
  foos = append(foos, &foo) // <= ここで変数 foo のポインタを参照し、foos に追加している
}

  1. range 文で何回繰り返そうと、左辺の変数は一度のみしか定義されない(同じ変数には破壊的代入を繰り返す)
  2. そのため、ブロック内の &foo は常に同じポインタ値を返す
  3. 3回の繰り返しのうち、すべて同じポインタが格納される
  4. foos はすべて同じポインタ値を格納しているため、参照時にすべて同じ変数を見る
  5. 3回目で fooFoo{”3”} が代入されるため、すべて Foo{"3"} を見る

対策

for ブロックの中で、左辺の変数のポインタを取得しない。

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0