Chapter 8

Enums and Pattern Matching

👋 Anyone can read and edit this exercise. Sign up to save your progress.

“Dad, why is my sister’s name Rose?”
“Because your mother loves roses.”
“Thanks, dad!”
“No problem, Rust enums.”

Dad is right, enums are the best. If you know the crippled form of enums in other languages (cough C), I'm so sorry for you. In Rust, they are a pure delight to work with.

An enum is a type whose value is one of a fixed set of variants. Think of it as a "this or that or that" type.

enum HttpStatus {
    Ok,
    NotFound,
    InternalServerError,
}

You typically inspect an enum value with match. This is what I like about match: the compiler checks that you've handled every variant. When you add a new variant later, it points you to each match that no longer covers every case.

fn code(status: HttpStatus) -> u16 {
    match status {
        HttpStatus::Ok => 200,
        HttpStatus::NotFound => 404,
        HttpStatus::InternalServerError => 500,
    }
}

Each arm of a match is pattern => expression. Multiple patterns can share an arm with |, and the catch-all is _:

match code {
    200 | 201 | 204 => "success",
    404 => "missing",
    _ => "something else",
}

#[derive(...)]: Free Implementations

You'll see this line on many types in Rust:

#[derive(Debug, PartialEq)]
enum HttpStatus {
    Ok,
    NotFound,
    InternalServerError,
}

The #[...] syntax is an attribute: extra instructions for the compiler attached to the item below. derive is the most common one. It says "please write the boilerplate for these capabilities for me." Each name inside the parentheses is a trait, Rust's name for a shared interface, similar to a Java interface or a Haskell type class. Traits are covered in more detail later.

For now, you need two:

Derive works on enums and structs whose fields all implement the same traits. For PartialEq on an enum, the generated implementation considers two values equal when they have the same variant and equal payloads. You can always write the implementation by hand instead when you need different behaviour.

Mapping Variants to Values

Write a match that turns each HttpStatus variant into the numeric code it represents. If you forget one, the compiler points to the incomplete match before the program can run.

After the tests pass, imagine adding TooManyRequests to the enum without changing your match. Will it compile? Predict the result, then try it and remove the extra variant. Would a _ catch-all hide the missing mapping?

Useful Resources

Exercise 1 of 2
Open in Web Editor

Results

    Compiler / runtime output
    
                
    Reveal the full solution Spoiler: the complete answer
    // `Copy` lets you pass the same `HttpStatus` value to multiple functions
    // without it being moved on the first call. Plain enums like this one (no
    // `String`, no `Vec`, no other heap data) are always cheap to copy, so deriving
    // `Copy` (and `Clone`) costs you nothing and removes a borrow-checker speed
    // bump.
    #[derive(Debug, PartialEq, Clone, Copy)]
    enum HttpStatus {
        Ok,
        NotFound,
        Unauthorized,
        InternalServerError,
        BadRequest,
    }
    
    /// Returns the HTTP status code number for the given status.
    fn status_code(status: HttpStatus) -> u16 {
        match status {
            HttpStatus::Ok => 200,
            HttpStatus::NotFound => 404,
            HttpStatus::Unauthorized => 401,
            HttpStatus::InternalServerError => 500,
            HttpStatus::BadRequest => 400,
        }
    }
    
    #[test]
    fn test_status_code() {
        assert_eq!(status_code(HttpStatus::Ok), 200);
        assert_eq!(status_code(HttpStatus::NotFound), 404);
        assert_eq!(status_code(HttpStatus::Unauthorized), 401);
        assert_eq!(status_code(HttpStatus::BadRequest), 400);
        assert_eq!(status_code(HttpStatus::InternalServerError), 500);
    }
    

    Matching One Variant

    Sometimes you only care about a single variant. You can still write a full match with a _ catch-all arm, or you can reach for the matches! macro. Both are idiomatic.

    For this exercise, return true only for InternalServerError, and false for every other variant.

    Useful from the Standard Library

    • std::matches! expands to a match that returns true for the given pattern and false otherwise. You can write it as matches!(status, HttpStatus::InternalServerError).
    • PartialEq enables ==, so the enum's derived implementation also lets you write status == HttpStatus::InternalServerError.
    Exercise 2 of 2
    Open in Web Editor

    Results

      Compiler / runtime output
      
                  
      Reveal the full solution Spoiler: the complete answer
      // `Copy` lets you pass the same `HttpStatus` value to multiple functions
      // without it being moved on the first call. Plain enums like this one (no
      // `String`, no `Vec`, no other heap data) are always cheap to copy, so deriving
      // `Copy` (and `Clone`) costs you nothing and removes a borrow-checker speed
      // bump.
      #[derive(Debug, PartialEq, Clone, Copy)]
      enum HttpStatus {
          Ok,
          NotFound,
          Unauthorized,
          InternalServerError,
          BadRequest,
      }
      
      /// Returns `true` if the request should be retried.
      ///
      /// Only retry on server errors, not client errors.
      fn should_retry(status: HttpStatus) -> bool {
          matches!(status, HttpStatus::InternalServerError)
      }
      
      #[test]
      fn test_should_retry() {
          assert_eq!(should_retry(HttpStatus::InternalServerError), true);
          assert_eq!(should_retry(HttpStatus::NotFound), false);
          assert_eq!(should_retry(HttpStatus::Unauthorized), false);
          assert_eq!(should_retry(HttpStatus::BadRequest), false);
          assert_eq!(should_retry(HttpStatus::Ok), false);
      }
      

      Wrapping Up Enums and Pattern Matching

      You defined an enum with a fixed set of variants, mapped each variant to a value with a match, and used matches! to ask a yes/no question about a single variant.

      What We Learned

      • An enum is a "this or that or that" type. Each value is exactly one of its variants, and the compiler tracks which one.
      • match checks a value against patterns top to bottom and runs the first arm that fits. Every arm is pattern => expression, and the whole match is itself an expression that produces a value.
      • match is exhaustive: leave a variant unhandled and the compiler refuses to build. Add a new variant later and every match that needs updating tells you exactly where.
      • | lets multiple patterns share an arm (200 | 201 | 204 => ...), and _ is the catch-all when you want to ignore the rest.
      • Derive Debug when you want {:?} printing and PartialEq when you want == comparisons. Deriving Clone, Copy lets callers reuse a value after passing it by value; Copy requires every field to be Copy too.
      • For a single-variant check, matches!(value, Variant) is the compact form; value == Variant works equally well when PartialEq is derived.
      Next chapter 9Vectors

      Optional Chapters

      Extra practice to explore at your own pace. These chapters do not count toward course progress.