Skip to content

Enums

Enums (enumerations) define a type that can be one of several named variants. Each variant can carry different data, making enums ideal for representing values that are one of a fixed set of possibilities.

Define an enum with the enum keyword:

enum Direction {
North,
South,
East,
West,
}

Each name inside the braces is a variant. Variants with no attached data are called unit variants.

Unit variants carry no payload. Construct them with EnumName::VariantName:

let dir = Direction::North
match dir {
Direction::North => print("heading north"),
Direction::South => print("heading south"),
Direction::East => print("heading east"),
Direction::West => print("heading west"),
}

A tuple variant carries a single unnamed value:

enum Shape {
Circle(number),
Square(number),
Rectangle(number, number),
}
let s = Shape::Circle(5.0)
let r = Shape::Rectangle(3.0, 4.0)

Extract the payload with pattern matching:

fn area(s: Shape) -> number {
match s {
Shape::Circle(r) => 3.14159 * r * r,
Shape::Square(side) => side * side,
Shape::Rectangle(w, h) => w * h,
}
}
print(area(Shape::Circle(5.0))) // 78.53975
print(area(Shape::Rectangle(3.0, 4.0))) // 12.0

Variants can also carry named fields:

enum Message {
Quit,
Move { x: int, y: int },
Write(string),
ChangeColor { r: int, g: int, b: int },
}
let msg = Message::Move { x: 10, y: 20 }

Match on struct-style variants by name:

fn handle(msg: Message) {
match msg {
Message::Quit => print("quit"),
Message::Move { x, y } => print(f"move to ({x}, {y})"),
Message::Write(text) => print(f"write: {text}"),
Message::ChangeColor { r, g, b } => print(f"color rgb({r},{g},{b})"),
}
}

match on an enum must cover every variant. The compiler enforces this:

enum Color { Red, Green, Blue }
let c = Color::Red
// This compiles — all three variants are covered:
match c {
Color::Red => print("red"),
Color::Green => print("green"),
Color::Blue => print("blue"),
}

If you miss a variant, the compiler reports a non-exhaustive match error.

Use _ to match any remaining variants when you don’t need to handle them all:

enum Status { Active, Inactive, Pending, Banned }
let s = Status::Pending
match s {
Status::Active => print("active"),
_ => print("not active"),
}

_ must be the last arm — it matches anything not already covered.

Shape’s standard library defines two fundamental enums you will encounter throughout the language.

Represents a value that may or may not be present:

enum Option<T> {
Some(T),
None,
}

Use it instead of null:

fn find_first(v: Vec<number>, predicate: (number) -> bool) -> Option<number> {
for x in v {
if predicate(x) {
return Some(x)
}
}
None
}
let result = find_first([1, 2, 3, 4, 5], |x| x > 3)
match result {
Some(val) => print(f"found: {val}"),
None => print("not found"),
}

Represents either success (Ok) or failure (Err):

enum Result<T, E> {
Ok(T),
Err(E),
}

The ? operator propagates errors automatically — see Error Handling for details.

fn parse_port(s: string) -> Result<int, string> {
let n = s.to_int()?
if n < 1 or n > 65535 {
return Err(f"port out of range: {n}")
}
Ok(n)
}
match parse_port("8080") {
Ok(port) => print(f"listening on port {port}"),
Err(msg) => print(f"invalid: {msg}"),
}

Enums automatically derive a Display implementation that prints the variant name. For unit variants this is just the name; for tuple and struct variants, the payload is included:

let d = Direction::North
print(d) // "North"
let s = Shape::Circle(3.0)
print(s) // "Circle(3.0)"

Override Display by implementing to_string() on the enum if you need custom formatting.