Returning structs seems to always return the same reference

Hi! I’m getting started with Odin and I don’t have a lot of experience with manually-managed memory languages.

I have trouble understanding how structs are passed around: I have a proc returning a struct with specific data, and it’s called inside a loop. What I end up seeing is that all returned structs are actually the same reference, so the code ends up updating the same struct over and over. Here is an example.

My understanding is that structs are allocated on the stack and when returned, they would be copied to the caller stack so they have a different reference. But I’m seeing that even a small struct with just an int field is actually not copied when returned.

Could someone please explain how structs are handled in memory? I think I’m missing a piece because it seems weird to me that for instance if you made a string builder inside a loop, you would not actually get a different builder each time.

Thank you!

Hello and welcome to the forum!

The lifetime of a stack variable is bound to the scope it’s declared in. In your case, that’s the for-loop. This means that the pointers you are storing in the list essentially point to garbage. In practise, the stack variable always gets the same memory which is why all your pointers are the same.

list := make([dynamic]^StructA, 0, 9)
for i in 1..<10 {
    foo := make_struct(i) // lifetime begins
    append(&list, &foo)
} // lifetime ends

There are multiple ways to solve your problem. The most obvious one would be to just store values in your list instead of pointers. This means that the list simply owns all the memory that your values live in. When you clear or destroy the list, the memory is freed.

list := make([dynamic]StructA, 0, 9) // note: not storing pointers
for i in 1..<10 {
    foo := make_struct(i)
    append(&list, foo) // value is copied into the list, all good
}

Alternatively, you could allocate those values with an allocator. This would mean that the list does not own the memory of the values so you might want to free all of those allocations at a later point.

list := make([dynamic]^StructA, 0, 9)
for i in 1..<10 {
    foo := make_struct(i)
    append(&list, foo) // note: foo is already a pointer, no & needed
}

make_struct :: proc(i: int) -> (result: ^StructA) { // note: returns pointer
    result = new(StructA)
    result^ = StructA{ a = i }
}

If you don’t have an actual reason against it, I recommend to just store values in the list (first suggestion above). There are more strategies, obviously. I just wanted to leave your example mostly intact and give a simple answer.

1 Like

Thank you for the detailed answer! I get it now; I think my misconception was that I didn’t realise the for loop had its own stack context and the struct was getting popped out of it on each loop.

Also, I have a cursor to the last created struct, and because I didn’t realise the copy of the struct was actually the value inserted into the list, I assigned it foo instead of assigning list[len(list) - 1] (or rather, the address of).

Thank you again for making it clear!

By the way, that’s off-topic but it seems that there’s no last proc for arrays while there is one for slices in core:slice. I’m curious about the reason why, or if maybe I’ve missed something?

1 Like

You can pass any array as slice to a proc. This is the suggested way. You can find more information here:

2 Likes

I didn’t get that slices where that cheap, thank you for pointing me to these resources!

1 Like