c3c
c3c copied to clipboard
Tagged unions?
Tagged unions are useful BUT you'd ideally also want:
- The ability to store the tag freely in the struct.
- Specify the size of the tag
- Switch over the tag.
- Get the tag "value" and store it somewhere else.
- Know the layout of the resulting union
- Have a syntax that is just slightly different from C3's other constructs.
- Have a syntax to allow them to be embedded as members in structs and untagged unions.
tagged union Foo {
int i;
const char *c;
};
Foo foo;
foo.i = 3;
@istag(foo.i) // => true
@istag(foo.c) // => false
foo.c = "hello";
@istag(foo.i) // => false
@istag(foo.c) // => true
switch(@tag(foo))
{
case Foo.i:
io::printf("Was %d\n", foo.i);
case Foo.c:
io::printf("Was %s\n", foo.c);
}
Alternative syntax etc:
struct Shape
{
int centerX;
int centerY;
byte kind; // Implicit enum
union (kind)
{
SQUARE: { int side; }
RECTANGLE: { int length, height; }
CIRCLE: { int radius; }
}
}
And another...
struct Shape
{
int centerX;
int centerY;
byte kind; // Implicit enum
union (kind)
{
struct square
{
int side;
}
struct rectangle
{
int length;
int height;
}
struct circle
{
int radius;
}
}
}
And yet another...
struct Shape
{
int centerX;
int centerY;
tagged union (kind)
{
case SQUARE:
int side;
case RECTANGLE:
int length;
int height;
case CIRCLE:
int radius;
}
byte kind;
}
tagged union Foo : int
{
int a;
int b;
char* c;
}
Foo f = ...
switch (f)
{
case Foo.a:
...
case Foo.b:
...
}
if (try f.a) { ... }
if (try char* c = f.c) { ... }
int x = f.a; // Implicit panic on fail
int! z = f.a; // optional on fail
int w = f.a ?? 123;
union Foo @tagged(int)
{
int a;
int b;
char* c;
}
...
Foo f = ...
switch (f.tag)
{
case Foo.a.tag:
...
}
if (f.tag == Foo.a.tag) { ... }
Another variant.
what about:
union Foo : int @tagged
{
int a;
int b;
char* c;
}
Foo f = ...
switch (f)
{
case Foo.a:
...
case Foo.b:
...
}
if (try f.a) { ... }
if (try char* c = f.c) { ... }
int x = f.a; // Implicit panic on fail
int! z = f.a; // optional on fail
int w = f.a ?? 123;
@pierrec That is also a possibility. But probably if there is try
etc with changes the semantics, then it needs to be a completely different type. So the question is if one should be able to change the type with an attribute.
My favourite is
struct Shape
{
byte kind; // Implicit enum
union (kind)
{
SQUARE: { int side; }
RECTANGLE: { int length, height; }
CIRCLE: { int radius; }
}
}
I think it strikes the right balance between explicitness and brevity. Another possibility is
struct Shape
{
union (byte kind) // implicit enum definition
{
SQUARE: { int side; }
RECTANGLE: { int length, height; }
CIRCLE: { int radius; }
}
}
this ensures that the tag variable definition cannot be separated from the union definition.
switch s.kind {
case SQUARE:
...
}
@DrGo The latter loses the ability to place the "kind" variable though, so that one cannot as efficiently pack the tagging enum.
// Size = 12
struct Shape
{
char kind;
union (kind)
{
SQUARE: { int side; }
RECTANGLE: { int length, height; }
CIRCLE: { int radius; }
}
}
// Also size = 12
struct Shape2
{
char kind;
char foo;
short bar;
union (kind)
{
SQUARE: { int side; }
RECTANGLE: { int length, height; }
CIRCLE: { int radius; }
}
}