Multi-pattern Matching

Rust allows matching against multiple patterns using the | operator. This feature can simplify code and reduce redundancy when multiple patterns lead to the same outcome.

Example: Multi-pattern Matching

fn check_number(num: i32) {
    match num {
        1 | 2 | 3 => println!("Number is between 1 and 3"),
        4..=10 => println!("Number is between 4 and 10"),
        _ => println!("Number is greater than 10 or less than 1"),
    }
}

fn main() {
    check_number(2); // Output: Number is between 1 and 3
    check_number(5); // Output: Number is between 4 and 10
    check_number(11); // Output: Number is greater than 10 or less than 1
}

In this example, the check_number function uses multi-pattern matching to handle different ranges of numbers succinctly.

Guards in Pattern Matching

Pattern guards allow additional conditions to be specified in a match arm. This feature is beneficial when you want to add more logic to your matching criteria without cluttering your code with nested matches.

Example: Using Guards

fn describe_number(num: i32) {
    match num {
        n if n < 0 => println!("Negative number"),
        n if n == 0 => println!("Zero"),
        n if n % 2 == 0 => println!("Even number"),
        _ => println!("Odd number"),
    }
}

fn main() {
    describe_number(-5); // Output: Negative number
    describe_number(0); // Output: Zero
    describe_number(4); // Output: Even number
    describe_number(7); // Output: Odd number
}

Here, the describe_number function leverages guards to differentiate between negative numbers, zero, even, and odd numbers, demonstrating the flexibility of pattern matching with conditions.

Nested Patterns

Nested patterns allow you to match complex data structures directly in the match statement. This can be particularly useful when working with tuples, structs, or enums that contain other data types.

Example: Nested Patterns with Structs

struct Point {
    x: i32,
    y: i32,
}

enum Shape {
    Circle(Point, f64), // Center and radius
    Rectangle(Point, Point), // Bottom-left and top-right corners
}

fn describe_shape(shape: Shape) {
    match shape {
        Shape::Circle(Point { x, y }, radius) => {
            println!("Circle at ({}, {}), radius: {}", x, y, radius);
        }
        Shape::Rectangle(Point { x1, y1 }, Point { x2, y2 }) => {
            println!("Rectangle from ({}, {}) to ({}, {})", x1, y1, x2, y2);
        }
    }
}

fn main() {
    let circle = Shape::Circle(Point { x: 0, y: 0 }, 5.0);
    let rectangle = Shape::Rectangle(Point { x: 1, y: 1 }, Point { x: 4, y: 4 });

    describe_shape(circle); // Output: Circle at (0, 0), radius: 5
    describe_shape(rectangle); // Output: Rectangle from (1, 1) to (4, 4)
}

In this example, the describe_shape function demonstrates how nested patterns can be used to destructure complex shapes, making the code cleaner and more understandable.

Combining Patterns and Guards

Combining multi-pattern matching with guards can lead to even more concise and expressive code. This technique allows for sophisticated matching logic while maintaining clarity.

Example: Combined Patterns and Guards

fn evaluate_expression(value: i32) {
    match value {
        1 | 2 | 3 if value % 2 == 0 => println!("Even number from 1 to 3"),
        1 | 2 | 3 => println!("Odd number from 1 to 3"),
        n if n > 3 => println!("Number greater than 3"),
        _ => println!("Number less than 1"),
    }
}

fn main() {
    evaluate_expression(1); // Output: Odd number from 1 to 3
    evaluate_expression(2); // Output: Even number from 1 to 3
    evaluate_expression(5); // Output: Number greater than 3
    evaluate_expression(0); // Output: Number less than 1
}

This example illustrates how combining multi-pattern matching with guards can lead to elegant solutions, allowing for both specificity and brevity.

Conclusion

Advanced pattern matching in Rust provides developers with powerful tools to write clean, efficient, and expressive code. By utilizing multi-pattern matching, guards, and nested patterns, you can enhance your Rust applications significantly. These techniques not only improve code readability but also reduce the potential for errors, making your codebase more maintainable.

Learn more with useful resources: