ExamplesBy LevelBy TopicLearning Paths
568 Fundamental

@ Bindings

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "@ Bindings" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Sometimes you want both to test a value against a pattern and to bind it to a name for use in the arm body. Key difference from OCaml: 1. **Syntax**: Rust uses `@` (before the pattern); OCaml uses `as` (after the pattern).

Tutorial

The Problem

Sometimes you want both to test a value against a pattern and to bind it to a name for use in the arm body. Without @ bindings, you would either have to test the pattern and reconstruct the value, or bind the name and recheck the condition inside the arm. The @ operator (at-binding) solves this: a @ 0..=12 both tests that the value is in range AND binds it to a, so you can use a in the expression without repeating the match.

🎯 Learning Outcomes

  • • How name @ pattern binds the value to name while also testing pattern
  • • How a @ 0..=12 combines range testing with binding
  • • How e @ Event::Click(..) binds the whole enum value while matching a variant
  • • How @ can be used in nested patterns and with guards
  • • Where @ is common: error reporting (bind the bad value), range-based classification
  • Code Example

    #![allow(clippy::all)]
    //! @ Bindings
    //!
    //! Binding a name while also matching a pattern.
    
    /// Bind while matching range.
    pub fn describe_age(age: u32) -> String {
        match age {
            a @ 0..=12 => format!("child ({})", a),
            a @ 13..=19 => format!("teen ({})", a),
            a @ 20..=64 => format!("adult ({})", a),
            a => format!("senior ({})", a),
        }
    }
    
    /// Bind while matching enum.
    #[derive(Debug)]
    pub enum Event {
        Click(i32, i32),
        KeyPress(char),
    }
    
    pub fn process_event(e: &Event) -> String {
        match e {
            e @ Event::Click(_, _) => format!("click: {:?}", e),
            e @ Event::KeyPress(_) => format!("key: {:?}", e),
        }
    }
    
    /// Bind while destructuring.
    pub fn first_two(v: &[i32]) -> Option<(i32, i32)> {
        match v {
            [first @ .., second, _] if v.len() >= 2 => Some((*first.first()?, *second)),
            _ => None,
        }
    }
    
    /// Bind with guards.
    pub fn check_value(n: i32) -> &'static str {
        match n {
            x @ 0..=10 if x % 2 == 0 => "small even",
            x @ 0..=10 => "small odd",
            x if x > 100 => "large",
            _ => "medium",
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_describe_age() {
            assert!(describe_age(5).contains("child"));
            assert!(describe_age(15).contains("teen"));
            assert!(describe_age(30).contains("adult"));
        }
    
        #[test]
        fn test_process_event() {
            let e = Event::Click(10, 20);
            assert!(process_event(&e).contains("click"));
        }
    
        #[test]
        fn test_check_value() {
            assert_eq!(check_value(4), "small even");
            assert_eq!(check_value(5), "small odd");
            assert_eq!(check_value(200), "large");
        }
    }

    Key Differences

  • Syntax: Rust uses @ (before the pattern); OCaml uses as (after the pattern).
  • Range testing: Rust a @ 0..=12 is common with range patterns; OCaml uses when guards for numeric ranges.
  • Nested bindings: Both support nested @/as bindings at multiple levels of a pattern.
  • Use case: Both languages most commonly use this when you need to both classify and display/log the matched value.
  • OCaml Approach

    OCaml uses as for the equivalent:

    let describe_age age = match age with
      | a when a <= 12 -> Printf.sprintf "child (%d)" a
      | a when a <= 19 -> Printf.sprintf "teen (%d)" a
      | a -> Printf.sprintf "adult (%d)" a
    
    (* Or with constructor binding: *)
    let f = function
      | (Some _ as x) -> Printf.printf "got %s\n" (match x with Some s -> s | None -> "")
      | None -> ()
    

    OCaml's as in patterns corresponds to Rust's @.

    Full Source

    #![allow(clippy::all)]
    //! @ Bindings
    //!
    //! Binding a name while also matching a pattern.
    
    /// Bind while matching range.
    pub fn describe_age(age: u32) -> String {
        match age {
            a @ 0..=12 => format!("child ({})", a),
            a @ 13..=19 => format!("teen ({})", a),
            a @ 20..=64 => format!("adult ({})", a),
            a => format!("senior ({})", a),
        }
    }
    
    /// Bind while matching enum.
    #[derive(Debug)]
    pub enum Event {
        Click(i32, i32),
        KeyPress(char),
    }
    
    pub fn process_event(e: &Event) -> String {
        match e {
            e @ Event::Click(_, _) => format!("click: {:?}", e),
            e @ Event::KeyPress(_) => format!("key: {:?}", e),
        }
    }
    
    /// Bind while destructuring.
    pub fn first_two(v: &[i32]) -> Option<(i32, i32)> {
        match v {
            [first @ .., second, _] if v.len() >= 2 => Some((*first.first()?, *second)),
            _ => None,
        }
    }
    
    /// Bind with guards.
    pub fn check_value(n: i32) -> &'static str {
        match n {
            x @ 0..=10 if x % 2 == 0 => "small even",
            x @ 0..=10 => "small odd",
            x if x > 100 => "large",
            _ => "medium",
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_describe_age() {
            assert!(describe_age(5).contains("child"));
            assert!(describe_age(15).contains("teen"));
            assert!(describe_age(30).contains("adult"));
        }
    
        #[test]
        fn test_process_event() {
            let e = Event::Click(10, 20);
            assert!(process_event(&e).contains("click"));
        }
    
        #[test]
        fn test_check_value() {
            assert_eq!(check_value(4), "small even");
            assert_eq!(check_value(5), "small odd");
            assert_eq!(check_value(200), "large");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_describe_age() {
            assert!(describe_age(5).contains("child"));
            assert!(describe_age(15).contains("teen"));
            assert!(describe_age(30).contains("adult"));
        }
    
        #[test]
        fn test_process_event() {
            let e = Event::Click(10, 20);
            assert!(process_event(&e).contains("click"));
        }
    
        #[test]
        fn test_check_value() {
            assert_eq!(check_value(4), "small even");
            assert_eq!(check_value(5), "small odd");
            assert_eq!(check_value(200), "large");
        }
    }

    Deep Comparison

    OCaml vs Rust: pattern at bindings

    See example.rs and example.ml for implementations.

    Exercises

  • Error with context: Write fn check_age(age: u32) -> Result<(), String> using a @ 130..=u32::MAX => Err(format!("age {} is unrealistic", a)) to include the invalid value in the error.
  • Nested @ binding: Match on Option<Option<i32>> using outer @ Some(inner @ Some(v)) and format all three bindings in the result string.
  • Struct field binding: Write match point { p @ Point { x, y } if x > 0 && y > 0 => format!("{:?} is in Q1", p), ... } — bind the whole struct while also checking individual fields.
  • Open Source Repos