sliceをrangeでループして取得した値のフィールドを更新しても、元のsliceは更新されない(rangeで取得した値はコピー)

AOJやっててハマったので

package main

import (
    "fmt"
)

type Element struct {
    number int
}

func main() {
    Elementのsliceの場合()
    Elementのポインタのsliceの場合()
}

func Elementのsliceの場合() {
    elements := []Element{}
    for i := 0; i < 2; i++ {
        elements = append(elements, Element{i})
    }
    fmt.Println(elements) // 0 1
    elements[0].number = 10
    fmt.Println(elements) // 10 1

    // これは99にならない
    for _, elem := range elements {
        elem.number = 99
    }
    fmt.Println(elements) // 10 1

    // これは99になる
    for i := 0; i < 2; i++ {
        elements[i].number = 99
    }
    fmt.Println(elements) // 99 99
}

func Elementのポインタのsliceの場合() {
    elements := []*Element{}
    for i := 0; i < 2; i++ {
        elements = append(elements, &Element{i})
    }
    fmt.Println(elements) // 0 1

    // これは99になる
    for _, elem := range elements {
        elem.number = 99
    }
    fmt.Printf("%v %v\n", elements[0].number, elements[1].number)
}

rangeで返ってくる値は要素のコピーになる(=参照が別)のかなと思ってポインタを見てみた感じ、そうっぽい

package main

import (
    "fmt"
)

type Element struct {
    number int
}

func main() {
    Elementのsliceの場合のポインタ()
    Elementのポインタのsliceの場合のポインタ()
}

func Elementのsliceの場合のポインタ() {
    elements := []Element{}
    for i := 0; i < 2; i++ {
        elements = append(elements, Element{i})
    }

    fmt.Println("各要素のポインタ:")
    fmt.Printf("element[0] pointer:%p\n", &elements[0])
    fmt.Printf("element[1] pointer:%p\n", &elements[1])

    fmt.Println("rangeの場合のポインタ:")
    for _, elem := range elements {
        // このポインタは&elements[0]とも&elements[1]とも異なる
        // さらに、ループ内で&elemはどの要素に対するポインタも同じ
        fmt.Printf("element[%v]] pointer:%p\n", elem.number, &elem)
    }

}

func Elementのポインタのsliceの場合のポインタ() {
    elements := []*Element{}
    for i := 0; i < 2; i++ {
        elements = append(elements, &Element{i})
    }
    fmt.Println("各要素のポインタ:")
    fmt.Printf("element[0] pointer:%p\n", &elements[0])
    fmt.Printf("element[1] pointer:%p\n", &elements[1])

    for _, elem := range elements {
        // このポインタは&elements[0]とも&elements[1]と等しい
        fmt.Printf("element[%v]] pointer:%p\n", elem.number, elem)
        // ループ内で&elemはどの要素に対するポインタも同じ(ポインタのポインタになってるはず)
        fmt.Printf("element[%v]] pointer:%p\n", elem.number, &elem)

    }
}

と思ったらtour of goにも書いてあった https://tour.golang.org/moretypes/16

When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.

channelも同じような感じ

package main

import "fmt"

type Element struct {
    number int
}

func send(c chan Element) {
    for i := 0; i < 3; i++ {
        elem := Element{i}
        fmt.Printf("send: %p\n", &elem)
        c <- elem
    }
    close(c)
}

// send: 0xc000094008
// send: 0xc000094018
// range: 0xc000094000
// range: 0xc000094000
// send: 0xc000018058
// range: 0xc000094000
func main() {
    c := make(chan Element)
    go send(c)

    for val := range c {
        fmt.Printf("range: %p\n", &val)
    }
}

関数に渡した場合にコピーになる奴でもちょっとハマったんだよな

どうするか

  • インデックスでループしてインデックスでアクセス
  • rangeでループしてインデックスでアクセス
  • ポインタの配列にする

あたり?