Append array of struct subtypes

Can anyone tell me why I get a compiler error when I try to append an array of a subtype to a dynamic array of a the supertype?

package experiment

Inner :: struct {
	a: int,
}

Outer :: struct {
	using inner: Inner,
}

doStuff :: proc() {
	inners: [dynamic]Inner

	// WORKS: I can append an array of 'Inners' (actually Outers)
	a := [2]Inner{Outer{}, Outer{}}
	append_elems(&inners, ..a[:])

	// ERROR: I cannot append an array of 'Outers'
	b := [2]Outer{Outer{}, Outer{}}
	append_elems(&inners, ..b[:]) // Cannot assign value 'outers[:]' of type '[]Outer' to '[]Inner' in a procedure argument
}

There are no subtypes or super types in Odin. You can cast an Outer into an Inner because of the using statement, but you cannot cast a []Outer into a []Inner.

The docs explicitly use the term “subtype” which is why I used it in my question. The docs say “It is possible to get subtype polymorphism…”

To achieve what I want, I would need to copy my array then?

Oh, my terminology might be off then! Okay, I’ll try to go a bit more into detail. First, let’s see what your example code actually does. To make things more obvious, I will add another field to the Outer struct.

package experiment

Inner :: struct {
	a: int,
}

Outer :: struct {
	using inner: Inner,
	b: int,
}

doStuff :: proc() {
	inners: [dynamic]Inner

	// This actually does not append the Outers to the array, but only copies
	// the 'inner' fields of the Outers into the array.
	// The 'b' fields will be completely gone this way.
	a := [2]Inner{Outer{}, Outer{}}
	append_elems(&inners, ..a[:])

	// ERROR: I cannot append an array of 'Outers'
	b := [2]Outer{Outer{}, Outer{}}
	append_elems(&inners, ..b[:]) // Cannot assign value 'outers[:]' of type '[]Outer' to '[]Inner' in a procedure argument
}

An array cannot hold values of different types. What is possible though is to store pointers to things of different types. This is actually also how for example C# does it since basically everything in C# is always a pointer anyway.

package experiment

import "core:fmt"

Inner :: struct {
	a: int,
}

Outer :: struct {
	using inner: Inner,
	b: int,
}

main :: proc() {
	inners: [dynamic]^Inner

	a := [2]Inner{Inner{a=1}, Inner{a=2}}
	for &inner in a do append_elem(&inners, &inner)

	b := [2]Outer{Outer{a=3, b=5}, Outer{a=4, b=6}}
	for &outer in b do append_elem(&inners, &outer)

	fmt.printfln("0: %v", inners[0])
	fmt.printfln("1: %v", inners[1])
	fmt.printfln("2: %v", cast(^Outer)inners[2])
	fmt.printfln("3: %v", cast(^Outer)inners[3])
}

Note that for the cast to work, the ‘inner’ field must be the first element of the Outer struct. The pointer we stored points to the inner field and if it’s the first field, then the address of the whole Outer is the same.

1 Like

Subtyping requires you use a pointer explicitly.

inners: [dynamic]^Inner

But there are a few caveats to note:

  • You have to track everything, allocations, the subtype, etc.
  • How you track things is up to you (e.g. enum, union, etc for the subtype).
1 Like