7

I want to convert a slice of string into slice of pointer to string

values1 := []string{"a", "b", "c"}
var values2 []*string
for _, v := range values1 {
    fmt.Printf("%p | %T\n", v, v)
    values2 = append(values2, &v)
}
fmt.Println(values2)

%!p(string=a) => string
%!p(string=b) => string
%!p(string=c) => string
[0xc42000e1d0 0xc42000e1d0 0xc42000e1d0]

From my understanding,
My variable v seems to be a string, not a pointer to string.
Therefore v should be copied from values1 when iterating.

Obviously I'm incorrect as v still have the same address 0xc42000e1d0.
How can v value change if it's not a pointer?

For a quick solution use:

values1 := []string{"a", "b", "c"}
var values2 []*string
for i, _ := range values1 {
    values2 = append(values2, &values1[i])
}
icza
  • 389,944
  • 63
  • 907
  • 827
Martin Magakian
  • 3,746
  • 5
  • 37
  • 53
  • 1
    What's the question? The first version doesn't work because there is only one loop variable (reused in each iteration), and you append the same address in each iteration. See examples [one](https://stackoverflow.com/questions/44715882/why-do-these-two-for-loop-variations-give-me-different-behavior/44716068#44716068) and [two](https://stackoverflow.com/questions/44044245/golang-register-multiple-routes-using-range-for-loop-slices-map/44045012#44045012). – icza Jan 26 '18 at 10:49
  • @icza updated with question – Martin Magakian Jan 26 '18 at 10:53

2 Answers2

12

The first version doesn't work as there is only one loop variable v, which has the same address in each iteration. The loop variable is assigned in each iteration, which changes its value but not its address, and you append this same address in each iteration.

A possible solution is what you proposed: to append the address of the proper slice element of the original slice:

for i := range values1 {
    values2 = append(values2, &values1[i])
}

This works, but note that values2 contains addresses pointing to the backing array of values1, so the backing array of values1 will be kept in memory (will not get garbage collected) until both values1 and values2 variables are unreachable. Also note that modifying elements of values1 will effect values2 indirectly, because elements of values2 point to elements of values1.

Another solution is to create temporary local variables and append the addresses of those:

for _, v := range values1 {
    v2 := v
    values2 = append(values2, &v2)
}

By doing this, your values2 slice will not keep values1 from getting garbage collected, also modifying values in values1 will not be reflected in values2, they will be independent.

See related questions about the loop variable:

Why do these two for loop variations give me different behavior?

Golang: Register multiple routes using range for loop slices/map

icza
  • 389,944
  • 63
  • 907
  • 827
2

The iteration values in a for .. range clause are assigned for each iteration.

The spec clearly states:

The iteration values are assigned to the respective iteration variables as in an assignment statement.

This is the equivalent of doing:

var v string
v = string1
values[0] = &v
v = string2
values[1] = &v
etc...

The variable v still lives in the same area of memory, it's just that the contents change. So the address of v never changes.

Marc
  • 19,394
  • 6
  • 47
  • 51