Without typed unions, its hard to make illegal states unrepresentable. For example, if we want to keep track of a Translink bus, you might want to restrict the state. If it is moving, you want the speed and the direction. If its stopped, you want to keep track of if the door is open. In this case, the practical way of doing this in go (without a billion lines of interfaces) is:
type BusState struct {
isMoving bool
speed int32
direction int32
doorOpen bool
}But, there's nothing stopping you from making a bus that is moving, but has an open door. Or a bus that is not moving but has a speed.
With typed enums in rust:
enum BusState {
Moving { speed: u32, direction: u32 }
Stopped { doorOpen: bool }
}I understand that was a dumb example, we should check if its moving based on the speed, etc
An even more simple example of when this is helpful is for optional types. If we want to represent an object that is either a uuid or empty (without pointers), in Go the most common way is this pattern:
type NullUUID struct {
value UUID
valid bool
}This sucks. Not in a world breaking way, but its uncomfortable. A bad programmer might just grab the UUID out of lazyness and forget to handle if valid is true or not. It is up to the user to ignore value if it isn't valid.
What about in rust?
Option<UUID>That's all. How? Here's the definition of the built in Option enum (a typed union, to be clear).
enum Option<T> {
Some(T),
None
}An Option<UUID> value is either a uuid, or nothing. We just don't even keep a uuid if we aren't in the Some variant. How do we unwrap it?
let maybeUUID: Option<UUID> = ?;
// code...
match maybeUUID {
Some(uuid) => {
println!("yay, i got the uuid! here it is: {}", uuid)
}
None => {
panic!("no uuid :(")
}
}It is impossible to read the uuid without making it completely clear that we checked for both cases (we can extract it other ways, but
no matter what you do its clear that we are only handing the Some variant.
I love typed unions.
Internally, typed unions will usually keep a byte at the beginning to specify the type, followed by however many bytes of the largest type in the union. That means you want to avoid making one variant that is massively larger than another, or you may waste a lot of memory (rust has a built in lint warning about this!!!)