ExamplesBy LevelBy TopicLearning Paths
564 Fundamental

Enum Variant Patterns

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Enum Variant Patterns" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Algebraic data types (ADTs) with sum-type variants are the backbone of functional programming. Key difference from OCaml: 1. **Unit variants**: Rust `Message::Quit` requires the enum name; OCaml `Quit` can be used unqualified in scope.

Tutorial

The Problem

Algebraic data types (ADTs) with sum-type variants are the backbone of functional programming. Enums in Rust are full ADTs: variants can carry no data (unit), named fields (struct-like), or positional fields (tuple-like). Pattern matching on enum variants is exhaustive — the compiler verifies every variant is handled. This makes enums with match the preferred tool for command dispatch, protocol parsing, event handling, and any domain with a finite set of cases. It is the functional alternative to class hierarchies with virtual dispatch.

🎯 Learning Outcomes

  • • How to match all four kinds of enum variants: unit, struct-like, tuple, and tuple-struct
  • • How if let provides non-exhaustive matching for a single variant of interest
  • • How matches! tests variant membership without extracting data
  • • How exhaustiveness checking catches missing variants at compile time
  • • Where enum patterns replace virtual dispatch: command handling, protocol state machines
  • Code Example

    #![allow(clippy::all)]
    //! Enum Variant Patterns
    //!
    //! Matching and destructuring enum variants.
    
    #[derive(Debug, PartialEq)]
    pub enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(u8, u8, u8),
    }
    
    /// Match all variants.
    pub fn process_message(msg: &Message) -> String {
        match msg {
            Message::Quit => "Quit".to_string(),
            Message::Move { x, y } => format!("Move to ({}, {})", x, y),
            Message::Write(text) => format!("Write: {}", text),
            Message::ChangeColor(r, g, b) => format!("Color: rgb({}, {}, {})", r, g, b),
        }
    }
    
    /// if let for single variant.
    pub fn is_quit(msg: &Message) -> bool {
        matches!(msg, Message::Quit)
    }
    
    /// Extract from specific variant.
    pub fn get_write_text(msg: &Message) -> Option<&str> {
        if let Message::Write(text) = msg {
            Some(text)
        } else {
            None
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_process() {
            assert_eq!(process_message(&Message::Quit), "Quit");
            assert!(process_message(&Message::Move { x: 1, y: 2 }).contains("1"));
            assert!(process_message(&Message::Write("hi".into())).contains("hi"));
        }
    
        #[test]
        fn test_is_quit() {
            assert!(is_quit(&Message::Quit));
            assert!(!is_quit(&Message::Write("x".into())));
        }
    
        #[test]
        fn test_get_write() {
            assert_eq!(
                get_write_text(&Message::Write("hello".into())),
                Some("hello")
            );
            assert_eq!(get_write_text(&Message::Quit), None);
        }
    }

    Key Differences

  • Unit variants: Rust Message::Quit requires the enum name; OCaml Quit can be used unqualified in scope.
  • Inline record variants: Rust struct-like variants Move { x, y } and OCaml inline records Move of {x: int; y: int} are equivalent.
  • **matches! vs boolean match**: Rust's matches! macro; OCaml achieves the same with (= Quit) or a function Quit -> true | _ -> false.
  • Exhaustiveness: Both compilers warn on missing variants; adding a new variant to the enum causes compile warnings in both.
  • OCaml Approach

    OCaml's variant types are the direct equivalent:

    type message = Quit | Move of {x: int; y: int} | Write of string | ChangeColor of int * int * int
    let process = function
      | Quit -> "Quit"
      | Move {x; y} -> Printf.sprintf "Move to (%d, %d)" x y
      | Write text -> "Write: " ^ text
      | ChangeColor (r, g, b) -> Printf.sprintf "Color: rgb(%d, %d, %d)" r g b
    

    Full Source

    #![allow(clippy::all)]
    //! Enum Variant Patterns
    //!
    //! Matching and destructuring enum variants.
    
    #[derive(Debug, PartialEq)]
    pub enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(u8, u8, u8),
    }
    
    /// Match all variants.
    pub fn process_message(msg: &Message) -> String {
        match msg {
            Message::Quit => "Quit".to_string(),
            Message::Move { x, y } => format!("Move to ({}, {})", x, y),
            Message::Write(text) => format!("Write: {}", text),
            Message::ChangeColor(r, g, b) => format!("Color: rgb({}, {}, {})", r, g, b),
        }
    }
    
    /// if let for single variant.
    pub fn is_quit(msg: &Message) -> bool {
        matches!(msg, Message::Quit)
    }
    
    /// Extract from specific variant.
    pub fn get_write_text(msg: &Message) -> Option<&str> {
        if let Message::Write(text) = msg {
            Some(text)
        } else {
            None
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_process() {
            assert_eq!(process_message(&Message::Quit), "Quit");
            assert!(process_message(&Message::Move { x: 1, y: 2 }).contains("1"));
            assert!(process_message(&Message::Write("hi".into())).contains("hi"));
        }
    
        #[test]
        fn test_is_quit() {
            assert!(is_quit(&Message::Quit));
            assert!(!is_quit(&Message::Write("x".into())));
        }
    
        #[test]
        fn test_get_write() {
            assert_eq!(
                get_write_text(&Message::Write("hello".into())),
                Some("hello")
            );
            assert_eq!(get_write_text(&Message::Quit), None);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_process() {
            assert_eq!(process_message(&Message::Quit), "Quit");
            assert!(process_message(&Message::Move { x: 1, y: 2 }).contains("1"));
            assert!(process_message(&Message::Write("hi".into())).contains("hi"));
        }
    
        #[test]
        fn test_is_quit() {
            assert!(is_quit(&Message::Quit));
            assert!(!is_quit(&Message::Write("x".into())));
        }
    
        #[test]
        fn test_get_write() {
            assert_eq!(
                get_write_text(&Message::Write("hello".into())),
                Some("hello")
            );
            assert_eq!(get_write_text(&Message::Quit), None);
        }
    }

    Deep Comparison

    OCaml vs Rust: pattern enum variants

    See example.rs and example.ml for implementations.

    Exercises

  • Command parser: Implement a Command enum with Move(Direction), Look, Take(String), Drop(String), Quit and write fn execute(cmd: Command, world: &mut World) -> String.
  • JSON value: Build a JsonValue enum covering Null, Bool(bool), Number(f64), Str(String), Array(Vec<JsonValue>), Object(Vec<(String, JsonValue)>) with a display function.
  • From string: Write fn parse_message(s: &str) -> Option<Message> that parses "quit", "move X Y", "write TEXT", "color R G B" into the Message enum.
  • Open Source Repos