Dynamic arrays and maps are both a small struct that contain a data pointer as well as some metadata.
https://pkg.odin-lang.org/base/runtime/#Raw_Dynamic_Array
Raw_Dynamic_Array :: struct {
data: rawptr, // effectively [^]T for [dynamic]T
len: int,
cap: int,
allocator: Allocator,
}
https://pkg.odin-lang.org/base/runtime/#Raw_Map
Raw_Map :: struct {
data: uintptr,
len: uintptr,
allocator: Allocator,
}
This is the reason why append
takes a pointer, for instance–it needs to be able to modify the length to account for the newly-added value, and also possibly update the data pointer and capacity if it needs to re-allocate. This data is not behind the pointer.
As mentioned, when you do testMap['t'] = t
, you are copying this struct (pointer + length + cap + allocator) into the map. You then set lookup
to point to t
–which is still the struct on the stack, not the one that got put into the map. The data at the pointer would not be cloned–both [dynamic]Test
s would point at the same array in memory, if they had allocated, but have different metadata on it. But just make([dynamic]Test)
doesn’t allocate, only set the allocator, so neither dynamic array has any backing memory yet.
When you then append to lookup
, the copy of the struct that’s on the stack is allocated and updated, but the array in the map isn’t updated. Worse, if there were an allocation and append
needed to re-allocate, the pointer in the map would still be pointing to the original, possibly-freed memory. In this case, both t
and the dynamic array in the map have an allocator set but have no allocations, so you avoid any use-after-frees in this case.
In your case, this manifests in two ways:
- When you print the map the first time, the length is still 0 in the
[dynamic]Test
in the map (since the copy in t
was updated instead), and its data pointer is still nil
and its capacity is 0. It is still actually empty; t
has diverged entriely.
- When you append to
lookup2
, it makes a separate allocation, since it wasn’t filled in with the allocation made by appending to lookup
. It points to completely different memory than lookup
.
This is also true of slices, but they aren’t often mutated in-place so it’s less apparent.
https://pkg.odin-lang.org/base/runtime/#Raw_Slice
Raw_Slice :: struct {
data: rawptr, // effectively [^]T for []T
len: int,
}