メメメモモ

プログラミング、リモートワーク生活、筋トレ、etc

Goのfor rangeでのポインタでハマったこと

概要

以下のプログラムの出力結果はどうなるでしょうか?

package main

import "fmt"

func main() {
    a := []string{"A", "B", "C"}
    var b []*string

    for _, str := range a {
        b = append(b, &str)
    }

    for _, str := range b {
        fmt.Println(*str)
    }
}

A B C の順番で出力されることを期待するかもしれませんが、実際は以下のようになります。

C
C
C

解説と検証コード

strがループ中にずっと同じポインタになっていることが原因です。

以下のプログラムで&strの値を出力してみます。
つまり、strが指している領域のアドレスを出力します。

package main

import "fmt"

func main() {
    a := []string{"A", "B", "C"}

    for _, str := range a {
        fmt.Println(&str)
    }
}

以下のように、ループ中に常に同じアドレスになっています。

0xc42000e1e0
0xc42000e1e0
0xc42000e1e0

つまり _, str := range aを実行するたびに、同じ領域の値を上書きしていっているということです。

最初のコード例では、bの配列に上記の結果が入るため、ループの最後で上書きされるCが出力されます。

意図した処理にするには?

インデックス変数を使うようにします。

package main

import "fmt"

func main() {
    a := []string{"A", "B", "C"}
    var b []*string

    n := len(a)
    for i := 0; i < n; i++ {
        b = append(b, &a[i])
    }

    for _, str := range b {
        fmt.Println(*str)
    }
}

以下のように意図した出力になります。

A
B
C

参考

qiita.com