Skip to Content
Course content

22: Enums and Pattern Matching

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Think about a vending machine. When you make a selection, the machine doesn't just get a "number"; it gets a specific type of request. Maybe you pressed the button for a "Soda," but that request carries more info: which flavor? Or maybe you hit "Snack," and that needs to know which row and column. Or, perhaps the machine is in an "Out of Order" state, where no selection is even possible.

In many languages, you'd handle this with a bunch of integers or strings and a lot of if/else blocks, hoping you didn't miss a case. In Rust, we use Enums (short for enumerations). But unlike enums in C or Java, Rust enums are "sum types," meaning they can actually hold data inside each variant. It's like a box that can be one of several different shapes, and each shape can hold different things.

Defining the Variants

Let's map that vending machine logic to actual code. I'll use a payment system example here because it's a classic scenario where you have a few distinct options, some of which need extra data.

enum PaymentMethod {
    Cash,                          // No extra data needed
    CreditCard(String),            // Needs a card number
    PayPal(String),                // Needs an email address
    Crypto { wallet: String, coin: String }, // Named fields for clarity
}

Notice how Cash is just a label, but CreditCard and PayPal are essentially tuples, and Crypto is like a mini-struct. This is the superpower of Rust enums: they let you group related options together, even if those options require completely different pieces of information.

Handling the Data with Match

Defining the enum is only half the battle. The real magic happens when you use match. I like to think of match as a strict accountant. It forces you to account for every single possibility defined in your enum. If you forget one, the compiler will yell at you before the code even runs.

fn process_payment(method: PaymentMethod) {
    match method {
        PaymentMethod::Cash => {
            println!("Handing over the change...");
        }
        PaymentMethod::CreditCard(number) => {
            println!("Charging card ending in {}...", &number[number.len()-4..]);
        }
        PaymentMethod::PayPal(email) => {
            println!("Redirecting to PayPal for {}...", email);
        }
        PaymentMethod::Crypto { wallet, coin } => {
            println!("Requesting {} from wallet {}...", coin, wallet);
        }
    }
}

See how we "destructure" the data? In the CreditCard arm, we aren't just checking if it's a credit card; we're extracting the number string and bringing it into the scope of that block. It's clean, it's safe, and it's impossible to accidentally try to access a "card number" when the user paid with cash.

The Shortcut for Single Cases

Now, I'll be honest: sometimes match feels like overkill. If you only care about one specific variant and want to ignore everything else, writing out every single arm is a chore. That's where if let comes in.

If you only care if the payment was via PayPal, you can do this:

if let PaymentMethod::PayPal(email) = method {
    println!("Sending a confirmation email to {}!", email);
} else {
    println!("Payment method doesn't support email notifications.");
}

It's essentially syntactic sugar for a match that has one specific arm and one big "catch-all" arm (the _ pattern). Use match when you need to be exhaustive; use if let when you're just hunting for one specific value.




📋 Practical Task

Building a Web API Response Processor

In real-world Rust, you'll frequently use enums to handle API responses. Your task is to build a small response processor.

Requirements:

  • Create an enum called ApiResponse with the following variants:
    • Success(String): Holds the response body.
    • ClientError(u16): Holds the HTTP status code (e.g., 404).
    • ServerError(u16): Holds the HTTP status code (e.g., 500).
    • Timeout: No extra data.
  • Write a function handle_response(response: ApiResponse) that uses a match statement to print a user-friendly message for each case:
    • For Success, print "Request succeeded: [body]".
    • For ClientError, print "Client error occurred. Status: [code]".
    • For ServerError, print "Server is having trouble. Status: [code]".
    • For Timeout, print "The request timed out. Please try again."
  • In your main function, create a vector of different ApiResponse variants and loop through them, passing each one to your handle_response function.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.