RickyA
RickyA

Reputation: 16029

After a golang range copy all items point to the last item of the src slice

After I copy all items from a slice (src) into a new slice (dst) all items in dst point to the last item of src.

package main

import (
    "fmt"
)

func main() {
    src := []string{"a", "b", "c"}
    dst := []*string{}
    for _, val := range src {
        dst = append(dst, &val)
    }

    for i, s := range dst {
        fmt.Printf("%v - %v\n", i, *s)
    }
}

>>> 0 - c
>>> 1 - c
>>> 2 - c

Why aren't "a" and "b" copied into the dst slice?

Upvotes: 3

Views: 841

Answers (2)

RickyA
RickyA

Reputation: 16029

The solution is simple, but counter intuitive in terms of what you would expect of the range statement.

The variable val is instantiated only once and holds the value of src[i] in every iteration. It does not has the actual pointer to src[i]. Instead the value of src[i] is copied to the memory that is assigned to val. By taking the pointer of val you get exactly that, and not the pointer to src[i].

package main

import (
    "fmt"
)

func main() {
    src := []string{"a", "b", "c"}
    dst := []*string{}
    for _, val := range src {
        dst = append(dst, &val)
    }

    for i, s := range dst {
        fmt.Printf("%v - %v - %p\n", i, *s, s)
    }

    //modify only contents of first index
    *dst[0] = "hi val"

    for i, s := range dst {
        fmt.Printf("%v - %v - %p\n", i, *s, s)
    }
}

>>>>
0 - c - 0x1040a120
1 - c - 0x1040a120
2 - c - 0x1040a120
0 - hi val - 0x1040a120
1 - hi val - 0x1040a120
2 - hi val - 0x1040a120

While in hindsight this is obvious - for _, val -> := <- range src - it can can bite you in the ass if you are unaware of it, so therefore this warning.

PS. this also applies to src as a map.

Upvotes: 7

OneOfOne
OneOfOne

Reputation: 99331

Adding to @RickyA's answer.

To achieve what you were trying to do you need to do:

for i := range src {
    dst[i] = &src[i]
}

Upvotes: 5

Related Questions