ExamplesBy LevelBy TopicLearning Paths
572 Fundamental

Ref Patterns

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Ref Patterns" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Before match ergonomics (Rust 2018), matching on a reference required explicit `ref` keywords in patterns to borrow rather than move the matched value. Key difference from OCaml: 1. **Explicit vs implicit**: Rust requires explicit `ref` (or relies on ergonomics) to borrow in patterns; OCaml always borrows implicitly.

Tutorial

The Problem

Before match ergonomics (Rust 2018), matching on a reference required explicit ref keywords in patterns to borrow rather than move the matched value. This was verbose and confusing, particularly for newcomers. Match ergonomics automated most cases, but explicit ref and ref mut still appear in older code, in code that must be explicit for clarity, and in specific contexts where ergonomics do not apply. Understanding both the old explicit style and the modern ergonomic style is essential for reading existing Rust codebases.

🎯 Learning Outcomes

  • • How Some(ref s) explicitly borrows s from a match on &Option<String>
  • • How match ergonomics (Some(s)) automatically infers s: &String when matching on &Option<T>
  • • How Some(ref mut s) creates a mutable reference binding
  • • How ref in struct patterns works: Point { ref x, ref y }
  • • Why understanding explicit ref is needed for reading pre-2018 Rust code
  • Code Example

    #![allow(clippy::all)]
    //! Ref Patterns
    //!
    //! Borrowing in patterns with ref and ref mut.
    
    /// Using ref to borrow.
    pub fn inspect(opt: &Option<String>) -> usize {
        match opt {
            Some(ref s) => s.len(),
            None => 0,
        }
    }
    
    /// Modern ergonomics (automatic ref).
    pub fn inspect_modern(opt: &Option<String>) -> usize {
        match opt {
            Some(s) => s.len(), // s is automatically &String
            None => 0,
        }
    }
    
    /// Using ref mut.
    pub fn append_exclaim(opt: &mut Option<String>) {
        match opt {
            Some(ref mut s) => s.push('!'),
            None => {}
        }
    }
    
    /// Ref in struct destructuring.
    pub struct Container {
        pub data: String,
    }
    
    pub fn peek(c: &Container) -> &str {
        let Container { ref data } = c;
        data
    }
    
    /// Ref vs move.
    pub fn demonstrate_ref() {
        let opt = Some(String::from("hello"));
    
        // With ref: borrow, opt still valid
        if let Some(ref s) = opt {
            println!("Borrowed: {}", s);
        }
        // opt still accessible
        assert!(opt.is_some());
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_inspect() {
            let opt = Some("hello".to_string());
            assert_eq!(inspect(&opt), 5);
            assert!(opt.is_some()); // still valid
        }
    
        #[test]
        fn test_inspect_modern() {
            let opt = Some("world".to_string());
            assert_eq!(inspect_modern(&opt), 5);
        }
    
        #[test]
        fn test_append() {
            let mut opt = Some("hi".to_string());
            append_exclaim(&mut opt);
            assert_eq!(opt, Some("hi!".to_string()));
        }
    
        #[test]
        fn test_peek() {
            let c = Container {
                data: "test".into(),
            };
            assert_eq!(peek(&c), "test");
        }
    }

    Key Differences

  • Explicit vs implicit: Rust requires explicit ref (or relies on ergonomics) to borrow in patterns; OCaml always borrows implicitly.
  • **ref mut**: Rust's ref mut creates a mutable reference — enables modifying the matched value in-place; OCaml uses mutable record fields or ref cells for the same effect.
  • Historical context: Pre-2018 Rust code uses ref extensively; modern code relies on ergonomics; OCaml code never used ref in patterns.
  • Mental model: Rust patterns explicitly model ownership and borrowing; OCaml patterns model structural decomposition without ownership concerns.
  • OCaml Approach

    OCaml always binds pattern variables by reference to the GC heap — there is no ref/ref mut distinction in patterns. Mutation requires ref cells in the value, not in the pattern:

    let inspect opt = match opt with
      | Some s -> String.length s  (* s is always a reference *)
      | None -> 0
    

    Full Source

    #![allow(clippy::all)]
    //! Ref Patterns
    //!
    //! Borrowing in patterns with ref and ref mut.
    
    /// Using ref to borrow.
    pub fn inspect(opt: &Option<String>) -> usize {
        match opt {
            Some(ref s) => s.len(),
            None => 0,
        }
    }
    
    /// Modern ergonomics (automatic ref).
    pub fn inspect_modern(opt: &Option<String>) -> usize {
        match opt {
            Some(s) => s.len(), // s is automatically &String
            None => 0,
        }
    }
    
    /// Using ref mut.
    pub fn append_exclaim(opt: &mut Option<String>) {
        match opt {
            Some(ref mut s) => s.push('!'),
            None => {}
        }
    }
    
    /// Ref in struct destructuring.
    pub struct Container {
        pub data: String,
    }
    
    pub fn peek(c: &Container) -> &str {
        let Container { ref data } = c;
        data
    }
    
    /// Ref vs move.
    pub fn demonstrate_ref() {
        let opt = Some(String::from("hello"));
    
        // With ref: borrow, opt still valid
        if let Some(ref s) = opt {
            println!("Borrowed: {}", s);
        }
        // opt still accessible
        assert!(opt.is_some());
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_inspect() {
            let opt = Some("hello".to_string());
            assert_eq!(inspect(&opt), 5);
            assert!(opt.is_some()); // still valid
        }
    
        #[test]
        fn test_inspect_modern() {
            let opt = Some("world".to_string());
            assert_eq!(inspect_modern(&opt), 5);
        }
    
        #[test]
        fn test_append() {
            let mut opt = Some("hi".to_string());
            append_exclaim(&mut opt);
            assert_eq!(opt, Some("hi!".to_string()));
        }
    
        #[test]
        fn test_peek() {
            let c = Container {
                data: "test".into(),
            };
            assert_eq!(peek(&c), "test");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_inspect() {
            let opt = Some("hello".to_string());
            assert_eq!(inspect(&opt), 5);
            assert!(opt.is_some()); // still valid
        }
    
        #[test]
        fn test_inspect_modern() {
            let opt = Some("world".to_string());
            assert_eq!(inspect_modern(&opt), 5);
        }
    
        #[test]
        fn test_append() {
            let mut opt = Some("hi".to_string());
            append_exclaim(&mut opt);
            assert_eq!(opt, Some("hi!".to_string()));
        }
    
        #[test]
        fn test_peek() {
            let c = Container {
                data: "test".into(),
            };
            assert_eq!(peek(&c), "test");
        }
    }

    Deep Comparison

    OCaml vs Rust: pattern ref patterns

    See example.rs and example.ml for implementations.

    Exercises

  • Pre-ergonomics rewrite: Take inspect_modern and rewrite it using explicit ref keywords — verify both versions compile and produce the same output.
  • Ref mut tree: Write a function fn negate_first(v: &mut Vec<i32>) using if let Some(ref mut first) = v.first_mut() { *first = -*first; }.
  • Struct ref pattern: Match on &Point { x, y } using both explicit ref x, ref y and ergonomic x, y — verify both bind x: &i32, y: &i32.
  • Open Source Repos