10.4 Using Enums in Code

Because enum variants can store different types of data, you must handle them carefully.

10.4.1 Pattern Matching with Enums

Rust’s pattern matching lets you compare a value against one or more patterns, binding variables to matched data. Once a pattern matches, the corresponding block runs:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn process_message(msg: Message) {
    match msg {
        Message::Quit => println!("Quit message"),
        Message::Move { x: 0, y: 0 } => println!("Not moving at all"),
        Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y),
        Message::Write(text) => println!("Write message: {}", text),
        Message::ChangeColor(r, g, b) => {
            println!("Change color to red: {}, green: {}, blue: {}", r, g, b)
        }
    }
}

fn main() {
    let msg = Message::Move { x: 0, y: 0 };
    process_message(msg);
}
  • Destructuring: Match arms can specify inner values, such as x: 0.
  • Order: The first matching pattern applies.
  • Completeness: Every variant must be handled or covered by a wildcard _.

We’ll explore advanced pattern matching techniques in Chapter 21.

10.4.2 The ‘if let’ Syntax

When you’re only interested in a single variant (and what to do if it matches), if let can be more concise than a full match.

Using match:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::Write(String::from("Hello"));
match msg {
    Message::Write(text) => println!("Message is: {}", text),
    _ => println!("Message is not a Write variant"),
}
}

Here, we don’t care about any variant other than Message::Write. The _ pattern covers everything else.

Using if let:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::Write(String::from("Hello"));
if let Message::Write(text) = msg {
    println!("Message is: {}", text);
} else {
    println!("Message is not a Write variant");
}
}
  1. if let Message::Write(text) = msg: Checks if msg is the Write variant. If so, text is bound to the contained String.
  2. else: Handles any variant that isn’t Message::Write.

You can chain multiple if let expressions with else if let:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::Move { x: 0, y: 0 };

    if let Message::Write(text) = msg {
        println!("Message is: {}", text);
    } else if let Message::Move { x: 0, y: 0 } = msg {
        println!("Not moving at all");
    } else {
        println!("Message is something else");
    }
}
  • else if let: Lets you check additional patterns in sequence. Each block only runs if its pattern matches and all previous conditions were not met.

In practice, when multiple variants must be handled, a full match is usually clearer and ensures you account for every possibility. However, for a single variant that needs special treatment, if let makes the code more concise and readable.

10.4.3 Methods on Enums

Enums can define methods in an impl block, just like structs:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {
        match self {
            Message::Quit => println!("Quit message"),
            Message::Move { x: 0, y: 0 } => println!("Not moving at all"),
            Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y),
            Message::Write(text) => println!("Write message: {}", text),
            Message::ChangeColor(r, g, b) => {
                println!("Change color to red: {}, green: {}, blue: {}", r, g, b)
            }
        }
    }
}

fn main() {
    let msg = Message::Move { x: 0, y: 0 };
    msg.call();
}
  • Encapsulation: Behavior is directly associated with the enum.
  • Internal Pattern Matching: Each variant is handled within the call method.