Unions with Parametric Polymorphism (Parapoly)
Error :: enum {Foo0, Foo1, Foo2}
Param_Union :: union($T: typeid) #no_nil {T, Error}
r: Param_Union(int)
r = 123
r = Error.Foo0
Union Casting
-
Limitations with Pointer Casting :
-
Caio:
-
how do I convert a 'pointer to a value', to a 'pointer to a union'? I'm doing
cast(^My_Union)value, wherevalue: ^$T, withTbeing a generic parameter for a procedure, but I'm getting an "index out of bounds" error while trying to cast a[2]f32
-
-
Blob:
-
you can't, unions are
<data><tag>. meaning they're bigger than their types. the tag is just an index into an array in the RTTI. -
except for unions with only a single pointer type
union{^T}where the tag is dropped & the nil check just checks if the pointer is nil.
-
-
-
Union type :
fmt.printfln("%T", my_union)
// or
fmt.printfln("%v", typeid_of(type_of(my_union)))
-
Unwrapped Union type :
fmt.printfln("%v", reflect.union_variant_typeid(my_union))
Type check
Via
value.(T)
Value :: union {
bool,
i32,
f32,
string,
}
v: Value
v = "Hellope"
// type assert that `v` is a `string` and panic otherwise.
s1 := v.(string)
// type assert but with an explicit BOOLEAN check. This will not panic.
s2, ok := v.(string)
if !ok {
// problem encountered.
}
Via Type Switch Statement
-
A type switch allows several type assertions in series.
-
A type switch is like a regular switch, but the cases are types (not values).
-
For a union, only the union's types are allowed as case types.
value: Value = ...
switch v in value {
case string:
#assert(type_of(v) == string)
case bool:
#assert(type_of(v) == bool)
case i32, f32:
// This case allows multiple types, therefore we cannot know which type to use
// `v` remains the original union value
#assert(type_of(v) == Value)
case:
// Default case
// In this case, it is `nil`
}
Maybe
-
Maybe .
-
A union which either returns a type
Tornil. In other languages, often seen asOption(T),Result(T), etc. -
Not used much, as Odin supports multiple return values.
halve :: proc(n: int) -> Maybe(int) {
if n % 2 != 0 do return nil
return n / 2
}
half, ok := halve(2).?
if ok do fmt.println(half) // 1
half, ok = halve(3).?
if !ok do fmt.println("3/2 isn't an int")
n := halve(4).? or_else 0
fmt.println(n) // 2
Low-Level
get_raw_union_tag :: proc(u: $T) -> u64
where intrinsics.type_is_union(T) {
PURE_MAYBE :: intrinsics.type_union_variant_count(T) == 1 && intrinsics.type_is_pointer(intrinsics.type_variant_type_of(T, 0))
OFFSET :: intrinsics.type_union_tag_offset(T)
TAG_T :: intrinsics.type_union_tag_type(T)
when PURE_MAYBE {
return u64(u != nil)
} else {
_u := u
return u64(((^TAG_T)(uintptr(&_u) + OFFSET))^)
}
}
-
The memory layout is payload + tag. Even though an
unionis just a struct with tag + raw_union, it can't be addressable viamy_union.tag, for example. -
I can know the tag type at comptime, as that's something that can be introspected based on the type definition, but I can't know the payload type unless using RTTI.
Tag Type
TAG_TYPE :: intrinsics.type_union_tag_type(type_of(my_union_var))
TAG_NAME :: intrinsics.type_canonical_name(TAG_TYPE)
-
TAG_TYPEis comptime andtypeid -
TAG_NAMEis comptime andstring
Payload Offset
OFFSET :: intrinsics.type_union_tag_offset(type_of(my_union_var)))
-
my_union_varcan be runtime. -
OFFSETis comptime anduintptr