ExamplesBy LevelBy TopicLearning Paths
574 Fundamental

if-let and while-let

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "if-let and while-let" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. `match` requires handling all cases, which is verbose when you only care about one. Key difference from OCaml: 1. **Conciseness**: `if let` is more concise than `match` for single

Tutorial

The Problem

match requires handling all cases, which is verbose when you only care about one. if let provides single-arm matching: execute a block only when the pattern matches, with an optional else for the non-matching case. while let loops while a pattern continues to match, perfect for draining queues, popping stacks, and processing iterators. These constructs are ubiquitous in real Rust code — understanding them and knowing when to prefer them over match is essential.

🎯 Learning Outcomes

  • • How if let Some(n) = opt { ... } binds and branches in one expression
  • • How if let chains work and when to prefer them over match
  • • How while let Some(x) = queue.pop() { ... } processes collections until empty
  • • How if let handles enum variants, Result, and custom patterns
  • • When to use matches! vs if let vs match for different use cases
  • Code Example

    #![allow(clippy::all)]
    //! if-let and while-let
    //!
    //! Conditional pattern matching.
    
    /// Basic if-let.
    pub fn describe_option(opt: Option<i32>) -> String {
        if let Some(n) = opt {
            format!("Got: {}", n)
        } else {
            "Nothing".to_string()
        }
    }
    
    /// if-let with else-if-let.
    pub fn categorize(opt: Option<i32>) -> &'static str {
        if let Some(n) = opt {
            if n > 0 {
                "positive"
            } else if n < 0 {
                "negative"
            } else {
                "zero"
            }
        } else {
            "none"
        }
    }
    
    /// if-let with enum.
    #[derive(Debug)]
    pub enum Message {
        Text(String),
        Number(i32),
        Empty,
    }
    
    pub fn extract_text(msg: &Message) -> Option<&str> {
        if let Message::Text(s) = msg {
            Some(s)
        } else {
            None
        }
    }
    
    /// while-let for iteration.
    pub fn sum_stack(mut stack: Vec<i32>) -> i32 {
        let mut sum = 0;
        while let Some(n) = stack.pop() {
            sum += n;
        }
        sum
    }
    
    /// Combining if-let with guards.
    pub fn check_value(opt: Option<i32>) -> &'static str {
        if let Some(n) = opt {
            if n > 100 {
                "large"
            } else {
                "small"
            }
        } else {
            "none"
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_describe() {
            assert!(describe_option(Some(5)).contains("Got"));
            assert!(describe_option(None).contains("Nothing"));
        }
    
        #[test]
        fn test_categorize() {
            assert_eq!(categorize(Some(5)), "positive");
            assert_eq!(categorize(Some(-3)), "negative");
            assert_eq!(categorize(None), "none");
        }
    
        #[test]
        fn test_extract_text() {
            let msg = Message::Text("hello".into());
            assert_eq!(extract_text(&msg), Some("hello"));
            assert_eq!(extract_text(&Message::Empty), None);
        }
    
        #[test]
        fn test_sum_stack() {
            assert_eq!(sum_stack(vec![1, 2, 3, 4, 5]), 15);
            assert_eq!(sum_stack(vec![]), 0);
        }
    
        #[test]
        fn test_check_value() {
            assert_eq!(check_value(Some(200)), "large");
            assert_eq!(check_value(Some(50)), "small");
        }
    }

    Key Differences

  • Conciseness: if let is more concise than match for single-arm cases; OCaml's match requires both arms but is equally concise.
  • while let: Rust's while let is idiomatic for draining collections; OCaml uses recursive functions or while loops with mutable state.
  • let chains: Rust (nightly) supports if let A = x && let B = y { ... } for chaining pattern checks; OCaml uses match nesting or Option.bind.
  • **matches! macro**: Rust's matches!(val, Pattern) is the most concise boolean check; OCaml uses a helper function or match val with Pat -> true | _ -> false.
  • OCaml Approach

    OCaml uses match with a single relevant arm:

    (* if let equivalent *)
    let describe_option opt =
      match opt with
      | Some n -> Printf.sprintf "Got: %d" n
      | None -> "Nothing"
    
    (* while let equivalent: functional recursion *)
    let rec drain queue =
      match Queue.pop_opt queue with
      | None -> ()
      | Some item -> process item; drain queue
    

    Full Source

    #![allow(clippy::all)]
    //! if-let and while-let
    //!
    //! Conditional pattern matching.
    
    /// Basic if-let.
    pub fn describe_option(opt: Option<i32>) -> String {
        if let Some(n) = opt {
            format!("Got: {}", n)
        } else {
            "Nothing".to_string()
        }
    }
    
    /// if-let with else-if-let.
    pub fn categorize(opt: Option<i32>) -> &'static str {
        if let Some(n) = opt {
            if n > 0 {
                "positive"
            } else if n < 0 {
                "negative"
            } else {
                "zero"
            }
        } else {
            "none"
        }
    }
    
    /// if-let with enum.
    #[derive(Debug)]
    pub enum Message {
        Text(String),
        Number(i32),
        Empty,
    }
    
    pub fn extract_text(msg: &Message) -> Option<&str> {
        if let Message::Text(s) = msg {
            Some(s)
        } else {
            None
        }
    }
    
    /// while-let for iteration.
    pub fn sum_stack(mut stack: Vec<i32>) -> i32 {
        let mut sum = 0;
        while let Some(n) = stack.pop() {
            sum += n;
        }
        sum
    }
    
    /// Combining if-let with guards.
    pub fn check_value(opt: Option<i32>) -> &'static str {
        if let Some(n) = opt {
            if n > 100 {
                "large"
            } else {
                "small"
            }
        } else {
            "none"
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_describe() {
            assert!(describe_option(Some(5)).contains("Got"));
            assert!(describe_option(None).contains("Nothing"));
        }
    
        #[test]
        fn test_categorize() {
            assert_eq!(categorize(Some(5)), "positive");
            assert_eq!(categorize(Some(-3)), "negative");
            assert_eq!(categorize(None), "none");
        }
    
        #[test]
        fn test_extract_text() {
            let msg = Message::Text("hello".into());
            assert_eq!(extract_text(&msg), Some("hello"));
            assert_eq!(extract_text(&Message::Empty), None);
        }
    
        #[test]
        fn test_sum_stack() {
            assert_eq!(sum_stack(vec![1, 2, 3, 4, 5]), 15);
            assert_eq!(sum_stack(vec![]), 0);
        }
    
        #[test]
        fn test_check_value() {
            assert_eq!(check_value(Some(200)), "large");
            assert_eq!(check_value(Some(50)), "small");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_describe() {
            assert!(describe_option(Some(5)).contains("Got"));
            assert!(describe_option(None).contains("Nothing"));
        }
    
        #[test]
        fn test_categorize() {
            assert_eq!(categorize(Some(5)), "positive");
            assert_eq!(categorize(Some(-3)), "negative");
            assert_eq!(categorize(None), "none");
        }
    
        #[test]
        fn test_extract_text() {
            let msg = Message::Text("hello".into());
            assert_eq!(extract_text(&msg), Some("hello"));
            assert_eq!(extract_text(&Message::Empty), None);
        }
    
        #[test]
        fn test_sum_stack() {
            assert_eq!(sum_stack(vec![1, 2, 3, 4, 5]), 15);
            assert_eq!(sum_stack(vec![]), 0);
        }
    
        #[test]
        fn test_check_value() {
            assert_eq!(check_value(Some(200)), "large");
            assert_eq!(check_value(Some(50)), "small");
        }
    }

    Deep Comparison

    OCaml vs Rust: pattern if let

    See example.rs and example.ml for implementations.

    Exercises

  • Stack drain: Write fn collect_stack(v: &mut Vec<i32>) -> Vec<i32> using while let Some(x) = v.pop() to collect elements in reverse order.
  • Chained if-let: Write a validation function using three chained if let checks that extract values from nested Option<Option<i32>> — compare with a let-else version.
  • Enum filter: Write fn keep_moves(events: Vec<Event>) -> Vec<(i32, i32)> using if let Event::Move { x, y } = e { ... } in a loop to collect only move events.
  • Open Source Repos