ExamplesBy LevelBy TopicLearning Paths
567 Fundamental

Pattern Binding Modes

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Pattern Binding Modes" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. When matching on a reference (e.g., `&Option<String>` instead of `Option<String>`), Rust historically required explicit `ref` keywords to borrow rather than move matched values. Key difference from OCaml: 1. **Explicit vs implicit**: Rust requires `ref` without ergonomics or when ergonomics do not apply; OCaml always binds by reference implicitly.

Tutorial

The Problem

When matching on a reference (e.g., &Option<String> instead of Option<String>), Rust historically required explicit ref keywords to borrow rather than move matched values. Match ergonomics (RFC 2005, Rust 2018) made this automatic in most cases: matching on &T adjusts the binding mode so Some(s) binds s: &String rather than requiring Some(ref s). Understanding binding modes explains many "borrowed vs moved" questions that arise when matching references, and helps write code that works correctly with both owned and borrowed match targets.

🎯 Learning Outcomes

  • • How ref explicitly creates a reference binding in a pattern
  • • How match ergonomics automatically applies ref when matching on &T
  • • How ref mut creates a mutable reference binding
  • • When explicit ref is still necessary vs when ergonomics handles it
  • • The difference between matching Option<String> (moves) vs &Option<String> (borrows)
  • Code Example

    #![allow(clippy::all)]
    //! Pattern Binding Modes
    //!
    //! ref, ref mut, and move in patterns.
    
    /// Move binding (default for owned).
    pub fn move_binding(opt: Option<String>) -> usize {
        match opt {
            Some(s) => s.len(), // s is moved
            None => 0,
        }
    }
    
    /// Ref binding (borrow).
    pub fn ref_binding(opt: &Option<String>) -> usize {
        match opt {
            Some(ref s) => s.len(), // s is &String
            None => 0,
        }
    }
    
    /// Modern: match ergonomics.
    pub fn ergonomic_binding(opt: &Option<String>) -> usize {
        match opt {
            Some(s) => s.len(), // s is automatically &String
            None => 0,
        }
    }
    
    /// Ref mut binding.
    pub fn ref_mut_binding(opt: &mut Option<String>) {
        if let Some(ref mut s) = opt {
            s.push_str("!");
        }
    }
    
    /// Binding with @.
    pub fn at_binding(n: i32) -> String {
        match n {
            x @ 1..=5 => format!("small: {}", x),
            x @ 6..=10 => format!("medium: {}", x),
            x => format!("other: {}", x),
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_move() {
            assert_eq!(move_binding(Some("hello".into())), 5);
        }
    
        #[test]
        fn test_ref() {
            let opt = Some("world".to_string());
            assert_eq!(ref_binding(&opt), 5);
            assert!(opt.is_some()); // still valid
        }
    
        #[test]
        fn test_ergonomic() {
            let opt = Some("test".to_string());
            assert_eq!(ergonomic_binding(&opt), 4);
        }
    
        #[test]
        fn test_ref_mut() {
            let mut opt = Some("hi".to_string());
            ref_mut_binding(&mut opt);
            assert_eq!(opt, Some("hi!".to_string()));
        }
    
        #[test]
        fn test_at() {
            assert!(at_binding(3).contains("small"));
            assert!(at_binding(7).contains("medium"));
        }
    }

    Key Differences

  • Explicit vs implicit: Rust requires ref without ergonomics or when ergonomics do not apply; OCaml always binds by reference implicitly.
  • Mutability: Rust's ref mut creates a mutable reference binding — a precise distinction; OCaml uses ref cells for mutable state.
  • Move vs borrow: Rust's binding mode determines whether the matched value is moved or borrowed — critical for ownership; OCaml has no moves.
  • Ergonomics scope: Match ergonomics only apply at the outer level of the pattern when matching on a reference — inner patterns may still need explicit ref.
  • OCaml Approach

    OCaml patterns always bind by reference to the GC heap — there is no move vs copy distinction:

    let ref_binding opt = match opt with
      | Some s -> String.length s
      | None -> 0
    (* s is always a reference to the string — no ref keyword needed *)
    

    Full Source

    #![allow(clippy::all)]
    //! Pattern Binding Modes
    //!
    //! ref, ref mut, and move in patterns.
    
    /// Move binding (default for owned).
    pub fn move_binding(opt: Option<String>) -> usize {
        match opt {
            Some(s) => s.len(), // s is moved
            None => 0,
        }
    }
    
    /// Ref binding (borrow).
    pub fn ref_binding(opt: &Option<String>) -> usize {
        match opt {
            Some(ref s) => s.len(), // s is &String
            None => 0,
        }
    }
    
    /// Modern: match ergonomics.
    pub fn ergonomic_binding(opt: &Option<String>) -> usize {
        match opt {
            Some(s) => s.len(), // s is automatically &String
            None => 0,
        }
    }
    
    /// Ref mut binding.
    pub fn ref_mut_binding(opt: &mut Option<String>) {
        if let Some(ref mut s) = opt {
            s.push_str("!");
        }
    }
    
    /// Binding with @.
    pub fn at_binding(n: i32) -> String {
        match n {
            x @ 1..=5 => format!("small: {}", x),
            x @ 6..=10 => format!("medium: {}", x),
            x => format!("other: {}", x),
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_move() {
            assert_eq!(move_binding(Some("hello".into())), 5);
        }
    
        #[test]
        fn test_ref() {
            let opt = Some("world".to_string());
            assert_eq!(ref_binding(&opt), 5);
            assert!(opt.is_some()); // still valid
        }
    
        #[test]
        fn test_ergonomic() {
            let opt = Some("test".to_string());
            assert_eq!(ergonomic_binding(&opt), 4);
        }
    
        #[test]
        fn test_ref_mut() {
            let mut opt = Some("hi".to_string());
            ref_mut_binding(&mut opt);
            assert_eq!(opt, Some("hi!".to_string()));
        }
    
        #[test]
        fn test_at() {
            assert!(at_binding(3).contains("small"));
            assert!(at_binding(7).contains("medium"));
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_move() {
            assert_eq!(move_binding(Some("hello".into())), 5);
        }
    
        #[test]
        fn test_ref() {
            let opt = Some("world".to_string());
            assert_eq!(ref_binding(&opt), 5);
            assert!(opt.is_some()); // still valid
        }
    
        #[test]
        fn test_ergonomic() {
            let opt = Some("test".to_string());
            assert_eq!(ergonomic_binding(&opt), 4);
        }
    
        #[test]
        fn test_ref_mut() {
            let mut opt = Some("hi".to_string());
            ref_mut_binding(&mut opt);
            assert_eq!(opt, Some("hi!".to_string()));
        }
    
        #[test]
        fn test_at() {
            assert!(at_binding(3).contains("small"));
            assert!(at_binding(7).contains("medium"));
        }
    }

    Deep Comparison

    OCaml vs Rust: pattern binding modes

    See example.rs and example.ml for implementations.

    Exercises

  • Explicit vs ergonomic: Write the same function twice — once using explicit ref and once relying on match ergonomics — verify they compile to the same behavior.
  • Ref mut mutation: Write fn append_exclaim(v: &mut Vec<String>) using for s in v.iter_mut() { ... } with ref mut inside the loop to mutate each string.
  • Mixed modes: Match on &(String, &str) — verify that (ref a, b) gives a: &String, b: &&str and that ergonomics alone gives a: &String, b: &&str without explicit ref.
  • Open Source Repos