10.4 Using Enums in Code: Pattern Matching

Since enum instances can represent different variants with potentially different data, you need a way to determine which variant you have and act accordingly. Rust’s primary tool for this is pattern matching using the match keyword.

10.4.1 The match Expression

A match expression compares a value against a series of patterns. When a pattern matches, the associated code block (the “arm”) executes. match in Rust is exhaustive: the compiler ensures all possible variants are handled.

#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

fn process_message(msg: Message) {
    // 'match' is an expression; its result can be used
    match msg {
        // Pattern for the Quit variant (no data to bind)
        Message::Quit => {
            println!("Quit message received.");
        }
        // Pattern matching specific values within a variant
        Message::Move { x: 0, y: 0 } => {
            println!("Move message: At the origin.");
        }
        // Pattern binding data fields to variables x and y
        Message::Move { x, y } => {
            println!("Move message: To coordinates x: {}, y: {}", x, y);
        }
        // Pattern binding tuple variant data to 'text'
        Message::Write(text) => {
            println!("Write message: '{}'", text);
            // 'text' is bound to the String inside Message::Write
        }
        // Pattern binding tuple variant data to r, g, b
        Message::ChangeColor(r, g, b) => {
            println!("ChangeColor message: R={}, G={}, B={}", r, g, b);
        }
        // No 'default' or '_' needed here because all Message
        // variants are explicitly handled. The compiler checks this!
    }
}

fn main() {
    let messages = vec![
        Message::Quit,
        Message::Move { x: 0, y: 0 }, // Will match the specific pattern first
        Message::Move { x: 15, y: 25 }, // Will match the general {x, y} pattern
        Message::Write(String::from("Pattern Matching Rocks!")),
        Message::ChangeColor(100, 200, 50),
    ];

    for msg in messages {
        // Note: 'messages' vector owns the String in Write.
        // 'process_message' takes ownership of 'msg'.
        println!("Processing: {:?}", msg); // Debug print before moving
        process_message(msg);
        println!("---");
    }
}
  • Patterns & Arms: Each VARIANT => { code } is a match arm. The part before => is the pattern.
  • Destructuring: Patterns can extract data from variants.
    • Message::Move { x, y } binds the fields x and y to local variables x and y.
    • Message::Write(text) binds the inner String to the local variable text.
    • Message::Move { x: 0, y: 0 } matches only if x is 0 and y is 0.
  • Order Matters: Arms are checked top-down. The first matching arm executes. Place specific patterns before more general ones.
  • Exhaustiveness: Forgetting a variant causes a compile-time error. Use the wildcard _ to handle remaining variants collectively if needed:
    // Hidden setup code for the wildcard example
    #[derive(Debug)]
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(u8, u8, u8),
    }
    fn process_message_partial(msg: Message) {
    match msg {
        Message::Quit => println!("Quitting."),
        Message::Write(text) => println!("Writing: {}", text.chars().count()),
        // The wildcard '_' matches any value not handled above
        _ => println!("Some other message type received."),
    }
    }
    fn main() {
      process_message_partial(Message::Quit);
      process_message_partial(Message::Move{ x: 1, y: 1});
      process_message_partial(Message::Write(String::from("Hi")));
    }
  • match is an Expression: A match evaluates to a value. All arms must return values of the same type.

Advanced pattern matching (guards, @ bindings) will be covered in Chapter 21.

10.4.2 Concise Control Flow with if let

When you only care about one specific variant, if let is typically more concise than a match expression that requires handling all other variants, often using a _ => {} catch-all arm.

Using match (for one variant):

// Hidden setup code
#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

fn main() {
let msg = Message::Write(String::from("Handle only this"));

match msg {
    Message::Write(text) => {
        println!("Handling Write message: {}", text);
    }
    _ => {} // Ignore all other variants silently
}
}

Using if let:

#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}
fn main() {
    let msg = Message::Write(String::from("Handle only this"));

    // Check if 'msg' matches the 'Message::Write' pattern
    if let Message::Write(text) = msg {
        // If it matches, 'text' is bound, and this block executes
        println!("Handling Write message via if let: {}", text);
        // Note: 'msg' is partially moved here if 'text' is not borrowed.
    } else {
        // Optional 'else' block executes if the pattern doesn't match
        println!("Not a Write message.");
    }

    let msg2 = Message::Quit;
    if let Message::Write(text) = msg2 {
         println!("This won't execute for msg2: {}", text);
    } else {
         println!("msg2 was not a Write message."); // This will execute
    }
}
  • Syntax: if let PATTERN = EXPRESSION { /* if matches */ } else { /* if not */ }
  • Functionality: Tests if EXPRESSION matches PATTERN. Binds variables on match. Executes the if block on match, else block otherwise.
  • Use Case: Convenient for handling one specific variant, optionally with an else for all others. Less boilerplate than match.

Chain else if let to handle a few specific cases sequentially:

#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

fn check_specific_messages(msg: Message) {
    if let Message::Quit = msg {
        println!("It's a Quit message.");
    } else if let Message::Move { x, y } = msg {
        println!("It's a Move message to ({}, {}).", x, y);
    } else {
        // Final else handles anything not matched above
        println!("It's some other message ({:?}).", msg);
    }
}

fn main() {
    check_specific_messages(Message::Move { x: 5, y: -5 });
    check_specific_messages(Message::Write(String::from("Hello")));
    check_specific_messages(Message::Quit);
}

For handling more than two or three variants or complex logic, a full match is usually clearer and leverages exhaustiveness checking better.

10.4.3 Defining Methods on Enums

Associate methods with an enum using an impl block, just like with structs, to encapsulate behavior.

#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

// Implementation block for the Message enum
impl Message {
    // Method taking an immutable reference to self
    fn describe(&self) -> String {
        // Use 'match' inside the method on 'self'
        match self {
            Message::Quit => "A Quit message".to_string(),
            Message::Move { x, y } => format!("A Move message to ({}, {})", x, y),
            Message::Write(text) => format!("A Write message: '{}'", text),
            Message::ChangeColor(r, g, b) =>
                format!("A ChangeColor message ({},{},{})", r, g, b),
        }
    }

    // Another method
    fn is_quit(&self) -> bool {
        // Match can directly return a boolean
        match self {
            Message::Quit => true,
            _ => false, // All other variants are not Quit
        }
    }
}

fn main() {
    let messages = vec![
        Message::Move { x: 1, y: 1 },
        Message::Quit,
        Message::Write(String::from("Method call example")),
    ];

    for msg in &messages { // Iterate over references (&Message)
        println!("Description: {}", msg.describe()); // Call method
        if msg.is_quit() {
            println!("    (Detected Quit message via method)");
        }
    }
}
  • Encapsulation: Methods group behavior with the enum definition.
  • self: Refers to the enum instance. Pattern matching on self is common within methods.