Shiklo Tools
PDF40
Image20
Calculators & Converters25
Text & Writing13
Developer26
Video14
Audio14
Document14
Data & Spreadsheet12
Web & SEO9
OCR & Scanning5
Archive & File12
Security & Privacy23
Health & Medical29
QR & Barcode5

Rust Enums

A struct says "this thing has a name and an age and marks". An enum says "this thing is either this or that or something else". Traffic lights are red, yellow or green. A payment is by cash, card or UPI. A direction is north, south, east or west. Whenever a value can be exactly one of a fixed list of possibilities, an enum is the right tool.

A simple enum

enum TrafficLight {
    Red,
    Yellow,
    Green,
}

fn advice(light: TrafficLight) -> &'static str {
    match light {
        TrafficLight::Red => "Stop",
        TrafficLight::Yellow => "Get ready",
        TrafficLight::Green => "Go",
    }
}

fn main() {
    println!("{}", advice(TrafficLight::Red));
    println!("{}", advice(TrafficLight::Yellow));
    println!("{}", advice(TrafficLight::Green));
}
Output
Stop
Get ready
Go

The possibilities, Red, Yellow and Green, are called variants. You write them with the enum's name, two colons and the variant: TrafficLight::Red. The &'static str return type means "text that lives for the whole program", which is what text written in quotes is. The lifetimes page explains that funny 'static mark.

match and enums are best friends

Remember that match must handle every possible value? With enums, that rule becomes a superpower. If you later add a fourth variant, say FlashingYellow, every match that forgot about it stops compiling and points at itself. You can never forget to handle a new case.

Variants can carry data

This is where Rust enums leave most other languages behind. Each variant can hold its own data, of its own shape:

enum Payment {
    Cash,
    Card { last_digits: u16 },
    Upi(String),
}

fn describe(p: &Payment) -> String {
    match p {
        Payment::Cash => String::from("paid in cash"),
        Payment::Card { last_digits } => format!("paid by card ending {}", last_digits),
        Payment::Upi(id) => format!("paid by UPI to {}", id),
    }
}

fn main() {
    let payments = vec![
        Payment::Upi(String::from("shop@bank")),
        Payment::Cash,
        Payment::Card { last_digits: 4821 },
    ];
    for p in &payments {
        println!("Order {}", describe(p));
    }
}
Output
Order paid by UPI to shop@bank
Order paid in cash
Order paid by card ending 4821

Cash carries nothing, Card carries a named field like a struct, and Upi carries a String like a tuple. In each match arm, the pattern unpacks the data into a variable you can use right away.

Methods on enums

Enums can have impl blocks too, exactly like structs:

enum Shape {
    Circle(f64),
    Square(f64),
}

impl Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle(r) => 3.14 * r * r,
            Shape::Square(side) => side * side,
        }
    }
}

fn main() {
    let shapes = [Shape::Circle(1.0), Shape::Square(3.0)];
    for s in &shapes {
        println!("Area: {:.2}", s.area());
    }
}
Output
Area: 3.14
Area: 9.00

Two enums from the standard library are so important that they get their own pages next: Option and Result.

Practice exercises

  1. Make an enum Direction with four variants and a function that returns the opposite direction.
  2. Add a Triangle(f64, f64) variant (base and height) to Shape and see the compiler point at the match until you handle it.
  3. Make an enum Message with variants Quit, Write(String) and Move { x: i32, y: i32 }, and print each one with match.