Struggling to create a union on the heap

Hi. Say I have this union:

Zing :: struct {}
Zong :: struct {}

Thing :: union {
    Zing,
    Zong,
}

I can create an instance of this union like this and it works fine:

do_thing :: proc(thing: ^Thing) {
    // doing things
}

main :: proc () {
    thing: Thing = Zing {}    
    do_zing(&thing)
}

But, supposing I wanted to create thing on the heap. This doesn’t work:

main :: proc () {
    thing: ^Thing = new(Zing)
    
    do_thing(thing)
}
$ odin build .
/home/plertrood/src/odin/heapedunions/main.odin(16:21) Error: Cannot assign value 'new(Zing)' of type '^Zing' to '^Thing' in variable declaration
        thing: ^Thing = new(Zing)

I can do stuff like:

main :: proc () {
    thing := new(Zing)
    do_thing(cast(^Thing)thing)
}

But that just creates code that core dumps.

I imagine casting doesn’t create the tagged union properly. It looks like there is some compiler magic happening when assigning to a variable that has the union type. But this doesn’t happen when dealing with a pointer to a union type. When I’m dealing with the heap, I only have pointers to deal with. So I’m not sure what the right way around this is. What am I missing?

After checking this with a debugger, it looks like the right tag number isn’t being written for the casted value, but I’m wondering if it’s even valid behavior to cast a pointer of a union member to the whole union.

Say you have a union U with one member A that is 8 bytes long and another member B that is 16 bytes long. If you pass cast(^U)^A to a procedure expecting ^U, it should have every right to expect that the memory backing ^U is large enough to fit any member.

Any attempt to convert ^U to store a B will overwrite neighboring memory because A was not large enough.

As far as workarounds go, I know that you can allocate unions on the heap and store members in them, and you can have unions of just pointers instead of pointers to unions. Either of those strategies should work.

I know that you can allocate unions on the heap and store members in them,

Ah interesting. Indeed I can:

    thing := new(Thing)

But I’m not quite sure what you mean by ‘store members in them’. I now have a pointer to a Thing, but I’m not sure how to then say this Thing is actually a Zing or a Zong

Making my union a union of pointers does working nicely though! Hadn’t thought to do that. Thanks.

Assign to the dereferenced pointer:

thing := new(Thing)
thing^ = Zing{42}

You can use a type switch statement to read or alter them later.

4 Likes

Ah! Amazing. Thank you!

Also you can do it another way:

Zing :: struct {}
Zong :: struct {}

Thing :: union {
    ^Zing,
    ^Zong,
}

main :: proc() {
    thing: Thing = new(Zing)
}

This way the union itself will remain on the stack (tag + ptr to Zing or Zong) whereas the data will be on the heap.

Whereas @Feoramund solution will result to the whole union being on the heap. Right solution depends on your usecase :slight_smile:

1 Like

Maybe this drawings can clarify what’s going on under the hood :upside_down_face:

2 Likes