Fixing C++ by making unions better
Jack Delahunt
Posted on July 12, 2022
In the past, I have gone through some of my issues with C++ and how I think I can improve the language. To do this I am currently creating my own language called ‘Liam’. I have gone over basic syntax and features in a past post here but today I am focusing on the new “Typed Unions” feature.
A full walkthrough of this in video form is on my channel here.
We have all experienced the deeply nested hierarchies of Object-Oriented Programming and asked ourselves “Is there a better way”? Well, I think C++ and as inherited from C already had a pretty good answer.
For me at least the vast majority of my inheritance comes down to wanting to group many different types into one with some common members or functions across them. For example, in Liam I have a ‘Statement’ struct that is inherited by many other statement types like ‘IfStatement’, ‘ScopeStatement’, and so on. It is nice to bundle these up and refer to them as just ‘Statement’ as I may not need or care about what I am referring to.
You can somewhat simulate this in C with the use of a union containing many structs and having some id of which type the union is currently.
struct Statement {
StatementType type;
union {
IfStatement if_statement;
ScopeStatement scope_statement;
LetStatement let_statement;
};
};
This gives the same functionality of inheritance but there is one issue, you must always check the if of the statement. While this is not normally unsafe it can lead to problems like when not initializing the type still gives a valid value.
To try and help with this I added Typed Unions to my language and here is how they work. You can see in the image below the type specifier for ‘x’ is multiple types separated by a pipe denoting and or semantic.
fn main(): void {
let x: u64 | bool | str = "hello sailor!";
}
This shows the value of x might be a number, a boolean, or a string but it is not known for sure. This can also be extended to struct types to replicate the behavior in C’s unions. But there is still one more thing and that is how to get the actual value from x in a type-safe way.
fn main(): void {
let x: u64 | bool | str = "hello sailor!";
if x is str as_string {
println[str](*as_string);
}
}
All you do is a ‘is expression’, this just returns true if the value currently stored in the variable is the one we are asking, and if so it gives us a pointer to that value which in this example is the ‘as_string’ identifier. This new identifier is also scoped to the if block meaning this can be repeated for multiple types.
Another area in which this is useful is error handling. I personally really love how Zig does error handling and these new Typed Unions can get us that.
fn main(): void {
if add(10, -20) is u64 n {
print[u64](*n);
}
}
fn add(x: i64, y: i64): u64 | str {
let result := x + y;
if result < 0 {
return "Error negative number";
}
}
This example is a bit more involved but you can see that the return of my add function is now either a number or a string where the string is my error message. I can then check if my function call was completed by using the is expression shown before.
If you like this post or want to contribute to the language go and check it out on github here.
Posted on July 12, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.