10.4 Using Enums in Code

10.4.1 Pattern Matching with Enums

Pattern matching involves comparing a value against a pattern and, if it matches, binding variables to the data within the value. Matching in Rust is done from top to bottom, and the first pattern that matches is selected.

Example: Handling Messages

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);
}

If the value matches a pattern, the code to the right of the => operator is executed. The code can use any bound variables. When the code contains more than a single statement, it must be enclosed in {}. The different branches of the match construct are separated by commas.

  • Destructuring with Values: We can match specific values within the data, such as x: 0, y: 0.
  • Order Matters: Since matching is top-down, the Message::Move { x: 0, y: 0 } pattern will catch moves where x and y are zero.
  • Default Cases: Patterns without specific values match any variant of that type.

We will discuss pattern matching in more detail in a later chapter.

10.4.2 The if let Syntax

The if let construct in Rust provides a concise and readable way to perform pattern matching when you're interested in a single pattern and want to execute code only if a value matches that pattern.

Example 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"),
}
}

Equivalent 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");
}
}

The if let construct allows you to combine pattern matching with conditional logic succinctly. It tests whether a value matches a specific pattern, and if it does, it executes the code within the if block, binding any variables in the pattern to the corresponding parts of the value. This is particularly useful when you only care about one particular pattern and don't need to handle other patterns exhaustively.

  • Simplifies Code: Avoids the need for a full match when only one pattern is of interest.

While the if let construct can be chained with else if for multiple patterns, it is typically used with a single if condition.

Example with else if:

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");
    }
}

10.4.3 Methods on Enums

You can define methods on enums using the impl block.

Example:

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: Methods allow you to encapsulate behavior related to the enum.
  • Pattern Matching Inside Methods: You can use match within methods to handle different variants.