Allocation idiom for returned values

I’ve seen this pattern in some code bases:

initialize_something :: proc(length: int) -> ^[]Something {

    sth := new([]Something)
    sth^ = make([]Something, length)
    // some more initialization stuff

    return sth

}

also as some sort of constructors for complex structs like this.

My understanding is that new doesn’t know the actual size of the data, so the allocator might actually choose a pointer for a segment that will be too small, and calling make could therefore overwrite some of the memory that comes after the chunk reserved for sth.

Is this pattern or idiom this unsafe or does the compiler ensure that no memory is overwritten? If so: how?

What happens here is that in new([]Something) you don’t allocate the slice data, you only allocate the fields of the slice, see Raw_Slice, and in make([]Something, len) you populate the sth.data and std.len.

Both sth and std.data is on the heap but they are separate allocations.

sth: ^[]Something --> []Something {data: rawptr, len: int}
                                    |
                                    |--> [^]Something
1 Like

Ah, so sth^ = data isn’t “store data where sth points to”, but “store the location of data in sth” (so equivalent to sth = &data)?

No, not quite. It’s the same as if you do

f := new(f32)
f^ = 42.0

You don’t copy &42.0, you copy the value into a chunk of memory of size_of(f32).
In this case, you copy the underlying representation of a slice (which is Raw_Slice).

Here, you can run this:

package foo

import "core:fmt"
import "base:runtime"

main :: proc() {
	x := new([]i32)
	y := make([]i32, 5)

	fmt.printfln("%p -> %v", x, transmute(^runtime.Raw_Slice)x)
	fmt.printfln("%v", transmute(runtime.Raw_Slice)y)

	x^ = y // same as x^ = make([]i32, 5)

	fmt.printfln("%p -> %v", x, transmute(^runtime.Raw_Slice)x)

	delete(x^) // free x.data first
	free(x) // free x itself
}

It will output something like this:

0x3DD9348 -> &Raw_Slice{data = 0x0, len = 0}
Raw_Slice{data = 0x3DD9378, len = 5}
0x3DD9348 -> &Raw_Slice{data = 0x3DD9378, len = 5}

Note, that y.data gets copied into x^.data and the same goes for the len field.

2 Likes

Ah, now it makes a lot of sense. Thank you!