Enums
Enums define types with a fixed set of possible values, called variants.
Defining Enums
enum Color {
Red,
Green,
Blue,
}
fn main() -> i32 {
let c = Color::Green;
0
}
Variants are accessed with the :: syntax: EnumName::VariantName.
Matching on Enums
Use match to handle each variant:
enum Color {
Red,
Green,
Blue,
}
fn color_value(c: Color) -> i32 {
match c {
Color::Red => 1,
Color::Green => 2,
Color::Blue => 3,
}
}
fn main() -> i32 {
@dbg(color_value(Color::Red)); // prints: 1
@dbg(color_value(Color::Green)); // prints: 2
@dbg(color_value(Color::Blue)); // prints: 3
0
}
Exhaustive Matching
Match expressions on enums must be exhaustive—you must handle all variants:
enum Direction {
North,
South,
East,
West,
}
fn to_degrees(d: Direction) -> i32 {
match d {
Direction::North => 0,
Direction::East => 90,
Direction::South => 180,
Direction::West => 270,
}
}
If you forget a variant, the compiler will tell you:
fn to_degrees(d: Direction) -> i32 {
match d {
Direction::North => 0,
Direction::East => 90,
// Error: non-exhaustive match, missing South and West
}
}
Use _ as a wildcard to match any remaining variants:
fn is_north(d: Direction) -> bool {
match d {
Direction::North => true,
_ => false,
}
}
Data Variants
Variants can carry associated data. Declare the field types in parentheses after the variant name:
enum IntOption {
Some(i32),
None,
}
fn main() -> i32 {
let x = IntOption::Some(42);
let y = IntOption::None;
0
}
An enum can mix unit variants (no data) and data variants freely.
Matching Data Variants
Use binding patterns to extract the data from a variant:
enum IntOption {
Some(i32),
None,
}
fn unwrap_or(opt: IntOption, default: i32) -> i32 {
match opt {
IntOption::Some(v) => v,
IntOption::None => default,
}
}
fn main() -> i32 {
let x = IntOption::Some(42);
let y = IntOption::None;
@dbg(unwrap_or(x, 0)); // prints: 42
@dbg(unwrap_or(y, 0)); // prints: 0
0
}
Use _ to discard a field you don't need:
enum Result {
Ok(i32),
Err(i32),
}
fn is_ok(r: Result) -> bool {
match r {
Result::Ok(_) => true,
Result::Err(_) => false,
}
}
Variants can carry multiple fields:
enum Event {
Click(i32, i32),
KeyPress(i32),
Quit,
}
fn describe(e: Event) -> i32 {
match e {
Event::Click(x, y) => x + y,
Event::KeyPress(code) => code,
Event::Quit => 0,
}
}
Struct Variants
Variants can also carry named fields, like a struct:
enum Shape {
Circle { radius: i32 },
Rectangle { width: i32, height: i32 },
Point,
}
fn main() -> i32 {
let s = Shape::Circle { radius: 5 };
0
}
Field init shorthand works here too—if a variable has the same name as a field, you can omit the : value part:
fn make_circle(radius: i32) -> Shape {
Shape::Circle { radius }
}
Matching Struct Variants
Use field bindings in braces to destructure struct variants. All fields must be listed:
enum Shape {
Circle { radius: i32 },
Rectangle { width: i32, height: i32 },
Point,
}
fn area(s: Shape) -> i32 {
match s {
Shape::Circle { radius } => radius * radius,
Shape::Rectangle { width, height } => width * height,
Shape::Point => 0,
}
}
fn main() -> i32 {
@dbg(area(Shape::Circle { radius: 5 })); // prints: 25
@dbg(area(Shape::Rectangle { width: 3, height: 4 })); // prints: 12
@dbg(area(Shape::Point)); // prints: 0
0
}
You can rebind a field to a different name with field: new_name, or discard it with field: _:
fn get_width(s: Shape) -> i32 {
match s {
Shape::Rectangle { width: w, height: _ } => w,
_ => 0,
}
}
Enums in Structs
Enums can be fields in structs:
enum Status {
Pending,
Active,
Completed,
}
struct Task {
id: i32,
status: Status,
}
fn is_done(task: Task) -> bool {
match task.status {
Status::Completed => true,
_ => false,
}
}
fn main() -> i32 {
let task = Task {
id: 1,
status: Status::Active,
};
@dbg(is_done(task)); // prints: 0 (false)
0
}
Generic Enums with Comptime
Just like structs, you can create generic enum types using comptime functions that return type. This uses anonymous enum syntax:
fn Option(comptime T: type) -> type {
enum {
Some(T),
None,
}
}
fn main() -> i32 {
let x: Option(i32) = Option(i32)::Some(42);
let y: Option(i32) = Option(i32)::None;
match x {
Option(i32)::Some(v) => @dbg(v), // prints: 42
Option(i32)::None => @dbg(0),
};
0
}
You can add methods to anonymous enums using Self to refer to the type being defined:
fn Option(comptime T: type) -> type {
enum {
Some(T),
None,
fn unwrap_or(self, default: T) -> T {
match self {
Self::Some(v) => v,
Self::None => default,
}
}
}
}
fn main() -> i32 {
let x = Option(i32)::Some(42);
let y = Option(i32)::None;
@dbg(x.unwrap_or(0)); // prints: 42
@dbg(y.unwrap_or(0)); // prints: 0
0
}
This is the idiomatic way to build reusable sum types in Gruel. See Comptime and Generics for more on comptime.
Example: Simple State Machine
enum TrafficLight {
Red,
Yellow,
Green,
}
fn next_light(current: TrafficLight) -> TrafficLight {
match current {
TrafficLight::Red => TrafficLight::Green,
TrafficLight::Green => TrafficLight::Yellow,
TrafficLight::Yellow => TrafficLight::Red,
}
}
fn light_duration(light: TrafficLight) -> i32 {
match light {
TrafficLight::Red => 30,
TrafficLight::Yellow => 5,
TrafficLight::Green => 25,
}
}