ExamplesBy LevelBy TopicLearning Paths
579 Fundamental

String Pattern Matching

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "String Pattern Matching" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Many programs dispatch on string commands: REPL commands, configuration keys, protocol keywords, HTTP methods. Key difference from OCaml: 1. **&str vs string**: Rust matches on `&str` slices; OCaml matches on `string` values — both are the natural string type in their language.

Tutorial

The Problem

Many programs dispatch on string commands: REPL commands, configuration keys, protocol keywords, HTTP methods. While HashMap lookup handles large vocabularies, small fixed sets are more readable as match expressions. Rust supports matching on &str literals, combining with or-patterns and guards for commands that start with a prefix. This pattern is common in CLI tools, game loops, and simple interpreters.

🎯 Learning Outcomes

  • • How match s { "quit" | "exit" => ... } matches string literal alternatives
  • • How guards s if s.starts_with('/') extend literal matching with predicates
  • • Why match on strings uses &str (not String) for pattern matching
  • • How to compare match vs if/else chains for string dispatch
  • • Where string match dispatch appears: CLI REPLs, command parsers, protocol handlers
  • Code Example

    fn classify_cmd(s: &str) -> &'static str {
        match s {
            "quit" | "exit" | "q" => "quit",
            "help" | "?" | "h" => "help",
            "" => "empty",
            _ => "unknown",
        }
    }

    Key Differences

  • &str vs string: Rust matches on &str slices; OCaml matches on string values — both are the natural string type in their language.
  • Prefix guards: Both use guards for prefix matching — Rust s.starts_with(...), OCaml String.sub s 0 1 = "/".
  • Exhaustiveness: Both require _ for strings since the domain is infinite.
  • Performance: Both compile string patterns to sequential comparisons — a HashMap is faster for large vocabularies.
  • OCaml Approach

    OCaml string matching is identical in syntax and semantics:

    let classify_cmd s = match s with
      | "quit" | "exit" | "q" -> "quit"
      | "help" | "?" | "h" -> "help"
      | s when String.length s > 0 && s.[0] = '/' -> "command"
      | "" -> "empty"
      | _ -> "unknown"
    

    Full Source

    #![allow(clippy::all)]
    //! # String Pattern Matching
    //!
    //! Match against string literals, with OR patterns and guards.
    
    /// Classify a command string.
    pub fn classify_cmd(s: &str) -> &'static str {
        match s {
            "quit" | "exit" | "q" => "quit",
            "help" | "?" | "h" => "help",
            s if s.starts_with('/') => "command",
            "" => "empty",
            _ => "unknown",
        }
    }
    
    /// Alternative using if-else chain.
    pub fn classify_cmd_if(s: &str) -> &'static str {
        if s == "quit" || s == "exit" || s == "q" {
            "quit"
        } else if s == "help" || s == "?" || s == "h" {
            "help"
        } else if s.starts_with('/') {
            "command"
        } else if s.is_empty() {
            "empty"
        } else {
            "unknown"
        }
    }
    
    /// Classify a day as weekday or weekend.
    pub fn day_type(d: &str) -> &'static str {
        match d {
            "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" => "weekday",
            "Saturday" | "Sunday" => "weekend",
            _ => "unknown",
        }
    }
    
    /// Alternative using array contains.
    pub fn day_type_array(d: &str) -> &'static str {
        const WEEKDAYS: [&str; 5] = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];
        const WEEKEND: [&str; 2] = ["Saturday", "Sunday"];
    
        if WEEKDAYS.contains(&d) {
            "weekday"
        } else if WEEKEND.contains(&d) {
            "weekend"
        } else {
            "unknown"
        }
    }
    
    /// Classify HTTP method.
    pub fn http_method(m: &str) -> &'static str {
        match m {
            "GET" => "read",
            "POST" | "PUT" | "PATCH" => "write",
            "DELETE" => "delete",
            _ => "unknown",
        }
    }
    
    /// Personalized greeting based on name patterns.
    pub fn greet(name: &str) -> String {
        match name {
            "Alice" => "Hello, Admin Alice!".into(),
            "" => "Hello, stranger!".into(),
            n if n.starts_with("Dr.") => format!("Good day, {}!", n),
            n => format!("Hi, {}!", n),
        }
    }
    
    /// Alternative using if-else.
    pub fn greet_if(name: &str) -> String {
        if name == "Alice" {
            "Hello, Admin Alice!".into()
        } else if name.is_empty() {
            "Hello, stranger!".into()
        } else if name.starts_with("Dr.") {
            format!("Good day, {}!", name)
        } else {
            format!("Hi, {}!", name)
        }
    }
    
    /// Parse a boolean string.
    pub fn parse_bool(s: &str) -> Option<bool> {
        match s.to_lowercase().as_str() {
            "true" | "yes" | "1" | "on" => Some(true),
            "false" | "no" | "0" | "off" => Some(false),
            _ => None,
        }
    }
    
    /// Match with case-insensitive comparison using guard.
    pub fn is_affirmative(s: &str) -> bool {
        match s {
            s if s.eq_ignore_ascii_case("yes") => true,
            s if s.eq_ignore_ascii_case("true") => true,
            s if s.eq_ignore_ascii_case("ok") => true,
            _ => false,
        }
    }
    
    /// Alternative using matches! and or_else.
    pub fn is_affirmative_simple(s: &str) -> bool {
        matches!(s.to_lowercase().as_str(), "yes" | "true" | "ok")
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_classify_cmd_quit() {
            assert_eq!(classify_cmd("quit"), "quit");
            assert_eq!(classify_cmd("exit"), "quit");
            assert_eq!(classify_cmd("q"), "quit");
        }
    
        #[test]
        fn test_classify_cmd_help() {
            assert_eq!(classify_cmd("help"), "help");
            assert_eq!(classify_cmd("?"), "help");
            assert_eq!(classify_cmd("h"), "help");
        }
    
        #[test]
        fn test_classify_cmd_slash() {
            assert_eq!(classify_cmd("/run"), "command");
            assert_eq!(classify_cmd("/help"), "command");
        }
    
        #[test]
        fn test_classify_cmd_empty_unknown() {
            assert_eq!(classify_cmd(""), "empty");
            assert_eq!(classify_cmd("foo"), "unknown");
        }
    
        #[test]
        fn test_classify_approaches_equivalent() {
            let cases = ["quit", "exit", "help", "?", "/run", "", "foo"];
            for s in cases {
                assert_eq!(classify_cmd(s), classify_cmd_if(s));
            }
        }
    
        #[test]
        fn test_day_type_weekday() {
            for d in ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] {
                assert_eq!(day_type(d), "weekday");
            }
        }
    
        #[test]
        fn test_day_type_weekend() {
            assert_eq!(day_type("Saturday"), "weekend");
            assert_eq!(day_type("Sunday"), "weekend");
        }
    
        #[test]
        fn test_day_type_unknown() {
            assert_eq!(day_type("Holiday"), "unknown");
            assert_eq!(day_type(""), "unknown");
        }
    
        #[test]
        fn test_day_type_approaches_equivalent() {
            let days = ["Monday", "Saturday", "Holiday", ""];
            for d in days {
                assert_eq!(day_type(d), day_type_array(d));
            }
        }
    
        #[test]
        fn test_http_method() {
            assert_eq!(http_method("GET"), "read");
            assert_eq!(http_method("POST"), "write");
            assert_eq!(http_method("PUT"), "write");
            assert_eq!(http_method("PATCH"), "write");
            assert_eq!(http_method("DELETE"), "delete");
            assert_eq!(http_method("WUT"), "unknown");
        }
    
        #[test]
        fn test_greet() {
            assert_eq!(greet("Alice"), "Hello, Admin Alice!");
            assert_eq!(greet(""), "Hello, stranger!");
            assert_eq!(greet("Dr.Smith"), "Good day, Dr.Smith!");
            assert_eq!(greet("Bob"), "Hi, Bob!");
        }
    
        #[test]
        fn test_greet_approaches_equivalent() {
            let names = ["Alice", "", "Dr.Smith", "Bob"];
            for n in names {
                assert_eq!(greet(n), greet_if(n));
            }
        }
    
        #[test]
        fn test_parse_bool() {
            assert_eq!(parse_bool("true"), Some(true));
            assert_eq!(parse_bool("TRUE"), Some(true));
            assert_eq!(parse_bool("yes"), Some(true));
            assert_eq!(parse_bool("1"), Some(true));
            assert_eq!(parse_bool("false"), Some(false));
            assert_eq!(parse_bool("no"), Some(false));
            assert_eq!(parse_bool("maybe"), None);
        }
    
        #[test]
        fn test_is_affirmative() {
            assert!(is_affirmative("yes"));
            assert!(is_affirmative("YES"));
            assert!(is_affirmative("Yes"));
            assert!(is_affirmative("true"));
            assert!(is_affirmative("ok"));
            assert!(!is_affirmative("no"));
            assert!(!is_affirmative("maybe"));
        }
    
        #[test]
        fn test_is_affirmative_approaches_equivalent() {
            let cases = ["yes", "YES", "true", "ok", "no", "maybe"];
            for s in cases {
                assert_eq!(is_affirmative(s), is_affirmative_simple(s));
            }
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_classify_cmd_quit() {
            assert_eq!(classify_cmd("quit"), "quit");
            assert_eq!(classify_cmd("exit"), "quit");
            assert_eq!(classify_cmd("q"), "quit");
        }
    
        #[test]
        fn test_classify_cmd_help() {
            assert_eq!(classify_cmd("help"), "help");
            assert_eq!(classify_cmd("?"), "help");
            assert_eq!(classify_cmd("h"), "help");
        }
    
        #[test]
        fn test_classify_cmd_slash() {
            assert_eq!(classify_cmd("/run"), "command");
            assert_eq!(classify_cmd("/help"), "command");
        }
    
        #[test]
        fn test_classify_cmd_empty_unknown() {
            assert_eq!(classify_cmd(""), "empty");
            assert_eq!(classify_cmd("foo"), "unknown");
        }
    
        #[test]
        fn test_classify_approaches_equivalent() {
            let cases = ["quit", "exit", "help", "?", "/run", "", "foo"];
            for s in cases {
                assert_eq!(classify_cmd(s), classify_cmd_if(s));
            }
        }
    
        #[test]
        fn test_day_type_weekday() {
            for d in ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] {
                assert_eq!(day_type(d), "weekday");
            }
        }
    
        #[test]
        fn test_day_type_weekend() {
            assert_eq!(day_type("Saturday"), "weekend");
            assert_eq!(day_type("Sunday"), "weekend");
        }
    
        #[test]
        fn test_day_type_unknown() {
            assert_eq!(day_type("Holiday"), "unknown");
            assert_eq!(day_type(""), "unknown");
        }
    
        #[test]
        fn test_day_type_approaches_equivalent() {
            let days = ["Monday", "Saturday", "Holiday", ""];
            for d in days {
                assert_eq!(day_type(d), day_type_array(d));
            }
        }
    
        #[test]
        fn test_http_method() {
            assert_eq!(http_method("GET"), "read");
            assert_eq!(http_method("POST"), "write");
            assert_eq!(http_method("PUT"), "write");
            assert_eq!(http_method("PATCH"), "write");
            assert_eq!(http_method("DELETE"), "delete");
            assert_eq!(http_method("WUT"), "unknown");
        }
    
        #[test]
        fn test_greet() {
            assert_eq!(greet("Alice"), "Hello, Admin Alice!");
            assert_eq!(greet(""), "Hello, stranger!");
            assert_eq!(greet("Dr.Smith"), "Good day, Dr.Smith!");
            assert_eq!(greet("Bob"), "Hi, Bob!");
        }
    
        #[test]
        fn test_greet_approaches_equivalent() {
            let names = ["Alice", "", "Dr.Smith", "Bob"];
            for n in names {
                assert_eq!(greet(n), greet_if(n));
            }
        }
    
        #[test]
        fn test_parse_bool() {
            assert_eq!(parse_bool("true"), Some(true));
            assert_eq!(parse_bool("TRUE"), Some(true));
            assert_eq!(parse_bool("yes"), Some(true));
            assert_eq!(parse_bool("1"), Some(true));
            assert_eq!(parse_bool("false"), Some(false));
            assert_eq!(parse_bool("no"), Some(false));
            assert_eq!(parse_bool("maybe"), None);
        }
    
        #[test]
        fn test_is_affirmative() {
            assert!(is_affirmative("yes"));
            assert!(is_affirmative("YES"));
            assert!(is_affirmative("Yes"));
            assert!(is_affirmative("true"));
            assert!(is_affirmative("ok"));
            assert!(!is_affirmative("no"));
            assert!(!is_affirmative("maybe"));
        }
    
        #[test]
        fn test_is_affirmative_approaches_equivalent() {
            let cases = ["yes", "YES", "true", "ok", "no", "maybe"];
            for s in cases {
                assert_eq!(is_affirmative(s), is_affirmative_simple(s));
            }
        }
    }

    Deep Comparison

    OCaml vs Rust: String Pattern Matching

    Basic String Matching

    OCaml

    let cmd s = match s with
      | "quit" | "exit" | "q" -> "quit"
      | "help" | "?" | "h"    -> "help"
      | ""                     -> "empty"
      | _                      -> "unknown"
    

    Rust

    fn classify_cmd(s: &str) -> &'static str {
        match s {
            "quit" | "exit" | "q" => "quit",
            "help" | "?" | "h" => "help",
            "" => "empty",
            _ => "unknown",
        }
    }
    

    String Matching with Guards

    OCaml

    let cmd s = match s with
      | s when String.length s > 0 && s.[0] = '/' -> "command"
      | _ -> "unknown"
    

    Rust

    fn classify_cmd(s: &str) -> &'static str {
        match s {
            s if s.starts_with('/') => "command",
            _ => "unknown",
        }
    }
    

    Key Differences

    AspectOCamlRust
    Syntaxmatch s withmatch s { }
    OR patterns"a" \| "b" -> ..."a" \| "b" => ...
    Guardswhen conditionif condition
    Binding in guards when f(s)s if f(s)
    String typestring (owned)&str (borrowed)
    Case insensitiveManual with String.lowercaseeq_ignore_ascii_case

    Working with Owned Strings

    Rust - Matching &String

    let owned = String::from("Monday");
    // &String derefs to &str automatically
    match owned.as_str() {
        "Monday" => "weekday",
        _ => "unknown",
    }
    // Or simply: match &owned[..] { ... }
    // Or: match &*owned { ... }
    

    String patterns in Rust work with &str. When you have a String, use .as_str() or deref coercion.

    Exercises

  • HTTP method dispatch: Write fn http_action(method: &str) -> &'static str matching "GET", "POST", "PUT", "DELETE", "PATCH" with an _ => "unsupported" fallthrough.
  • Prefix routing: Implement fn route(path: &str) -> &'static str using guards for paths starting with "/api/", "/admin/", "/static/" — and exact matches for "/" and "/health".
  • Case-insensitive: Modify classify_cmd to handle uppercase variants using s if s.eq_ignore_ascii_case("quit") guards.
  • Open Source Repos