A union with an added tag
to keep track of type of field currently being used. A Tagged Union can be created and typechecked through a metaprogram.
We start with the struct definition. The fields
parameter in the Tag_Union
represents the name of the field you want. The types
are the field’s corresponding name:
Tag_Union :: struct(fields: [] string, types: []Type) {
tag: Type;
#insert -> string {
builder: String_Builder;
defer free_buffers(*builder);
count := fields.count - 1;
print_to_builder(*builder, "union {\n");
for i: 0..count {
print_to_builder(*builder, " %1: %2;\n", fields[i], types[i]);
}
print_to_builder(*builder, "}\n");
result := builder_to_string(*builder);
return result;
}
}
And define a set
function for the Tagged Union:
set :: (u: *$Tag/Tag_Union, value: $T) {
#insert -> string {
count := u.fields.count - 1;
for i: 0..count {
if T == Tag.types[i] {
code :: #string END
u.tag = type_of(value);
u.% = value;
END
return sprint(code, Tag.fields[i]);
}
}
assert(false, "Invalid value: %\n", T);
return "assert(false)";
}
}
With both of these definitions, we can define and use a tagged union as follows:
fields :: string.["int_a", "float_b", "string_c"];
types :: Type.[int, float, string];
tag_union: Tag_Union(fields, types);
set(*tag_union, 10);
print("tag_union.int_a=% tag_union.tag=%\n", tag_union.int_a, tag_union.tag);
set(*tag_union, 3.14);
print("tag_union.float_b=% tag_union.tag=%\n", tag_union.float_b, tag_union.tag);
set(*tag_union, "Han Solo");
print("tag_union.string_c=% tag_union.tag=%\n", tag_union.string_c, tag_union.tag);