Enums as Algebraic Data Types
TypeScript unions are structural — Rust enums carry data
Section titled “TypeScript unions are structural — Rust enums carry data”TypeScript has union types ("circle" | "rectangle") and discriminated unions (objects with a kind field). These are powerful, but they are a convention — the compiler trusts you to set the discriminant correctly.
Rust enums are algebraic data types (ADTs). Each variant can carry its own data — different types per variant. The compiler enforces exhaustive pattern matching: if you add a new variant, every match in your codebase that handles the enum must be updated or the code will not compile.
Enums with data
Section titled “Enums with data”// TypeScript — discriminated union (convention-based)type Shape = | { kind: "circle"; radius: number } | { kind: "rectangle"; width: number; height: number } | { kind: "triangle"; base: number; height: number };
function area(shape: Shape): number { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "rectangle": return shape.width * shape.height; case "triangle": return 0.5 * shape.base * shape.height; // If you add a new variant and forget a case, TypeScript // will warn only if you use a never-check pattern. }}#[derive(Debug)]enum Shape { Circle(f64), Rectangle(f64, f64), Triangle { base: f64, height: f64 },}
impl Shape { fn area(&self) -> f64 { match self { Shape::Circle(r) => std::f64::consts::PI * r * r, Shape::Rectangle(w, h) => w * h, Shape::Triangle { base, height } => 0.5 * base * height, } }}
fn main() { let shapes = vec![ Shape::Circle(3.0), Shape::Rectangle(4.0, 5.0), Shape::Triangle { base: 6.0, height: 4.0 }, ]; for s in &shapes { println!("{:?} => area = {:.2}", s, s.area()); }}Enums for state machines
Section titled “Enums for state machines”Enums are the idiomatic way to model state machines in Rust. Each state variant carries only the data relevant to that state:
// TypeScript — state machine with discriminated uniontype ConnectionState = | { status: "disconnected" } | { status: "connecting"; attempt: number } | { status: "connected"; sessionId: string };
function describe(state: ConnectionState): string { switch (state.status) { case "disconnected": return "not connected"; case "connecting": return `attempt ${state.attempt}`; case "connected": return `session ${state.sessionId}`; }}enum ConnectionState { Disconnected, Connecting { attempt: u32 }, Connected { session_id: String },}
fn describe(state: &ConnectionState) -> String { match state { ConnectionState::Disconnected => "not connected".to_string(), ConnectionState::Connecting { attempt } => format!("attempt {}", attempt), ConnectionState::Connected { session_id } => format!("session {}", session_id), }}
fn main() { let states = vec![ ConnectionState::Disconnected, ConnectionState::Connecting { attempt: 3 }, ConnectionState::Connected { session_id: "abc-123".to_string() }, ]; for s in &states { println!("{}", describe(s)); }}Try it
Section titled “Try it”#[derive(Debug)]enum Shape { Circle(f64), Rectangle(f64, f64), Triangle { base: f64, height: f64 },}
impl Shape { fn area(&self) -> f64 { match self { Shape::Circle(r) => std::f64::consts::PI * r * r, Shape::Rectangle(w, h) => w * h, Shape::Triangle { base, height } => 0.5 * base * height, } }}
fn main() { let shapes = vec![ Shape::Circle(3.0), Shape::Rectangle(4.0, 5.0), Shape::Triangle { base: 6.0, height: 4.0 }, ]; for s in &shapes { println!("{:?} => area = {:.2}", s, s.area()); }}Compiling…