Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang Reusing Memory Address Copying from slice?

Tags:

pointers

go

I was hitting an issue in a project I'm working on. I found a way around it, but I wasn't sure why my solution worked. I'm hoping that someone more experience with how Go pointers work could help me.

I have a Model interface and a Region struct that implements the interface. The Model interface is implemented on the pointer of the Region struct. I also have a Regions collection which is a slice of Region objects. I have a method that can turn a Regions object into a []Model:

// Regions is the collection of the Region model
type Regions []Region

// Returns the model collection as a list of models
func (coll *Regions) ToModelList() []Model {
    output := make([]Model, len(*coll))
    for idx, item := range *coll {
        output[idx] = &item
    }
    return output
}

When I run this code, I end up with the first pointer to the Region outputted multiple times. So, if the Regions collection has two distinct items, I will get the same address duplicated twice. When I print the variables before I set them in the slice, they have the proper data.

I messed with it a little bit, thinking Go might be reusing the memory address between loops. This solution is currently working for me in my tests:

// Returns the model collection as a list of models
func (coll *Regions) ToModelList() []Model {
    output := make([]Model, len(*coll))
    for idx, _ := range *coll {
        i := (*coll)[idx]
        output[idx] = &i
    }
    return output
}

This gives the expected output of two distinct addresses in the output slice.

This honestly seems like a bug with the range function reusing the same memory address between runs, but I always assume I'm missing something in cases like this.

I hope I explained this well enough for you. I'm surprised that the original solution did not work.

Thanks!

like image 877
jnadro52 Avatar asked Apr 28 '15 15:04

jnadro52


2 Answers

In your first (non working) example item is the loop variable. Its address is not changing, only its value. That's why you get the same address in output idx times.

Run this code to see the mechanics in action;

func main() {

    coll := []int{5, 10, 15}

    for i, v := range coll {
       fmt.Printf("This one is always the same; %v\n", &v)
       fmt.Println("This one is 4 bytes larger each iteration; %v\n", &coll[i])
    }
}
like image 200
evanmcdonnal Avatar answered Oct 05 '22 07:10

evanmcdonnal


There is just one item variable for the entire loop, which is assigned the corresponding value during each iteration of the loop. You do not get a new item variable in each iteration. So you are just repeatedly taking the address of the same variable, which will of course be the same.

On the other hand, if you declared a local variable inside the loop, it will be a new variable in each iteration, and the addresses will be different:

for idx, item := range *coll {
    temp := item
    output[idx] = &temp
}
like image 20
newacct Avatar answered Oct 05 '22 07:10

newacct