Skip to content

Instantly share code, notes, and snippets.

@ryanwarfield
Created April 10, 2026 20:42
Show Gist options
  • Select an option

  • Save ryanwarfield/c8d67d53f6ce5ccad107544b9907b630 to your computer and use it in GitHub Desktop.

Select an option

Save ryanwarfield/c8d67d53f6ce5ccad107544b9907b630 to your computer and use it in GitHub Desktop.
Why typed unions are the best thing ever.

Typed Unions!

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.

Epilogue

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!!!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment