Ref Patterns
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
Some(ref s) explicitly borrows s from a match on &Option<String>Some(s)) automatically infers s: &String when matching on &Option<T>Some(ref mut s) creates a mutable reference bindingref in struct patterns works: Point { ref x, ref y }ref is needed for reading pre-2018 Rust codeCode 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
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.ref extensively; modern code relies on ergonomics; OCaml code never used ref in patterns.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");
}
}#[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
inspect_modern and rewrite it using explicit ref keywords — verify both versions compile and produce the same output.fn negate_first(v: &mut Vec<i32>) using if let Some(ref mut first) = v.first_mut() { *first = -*first; }.&Point { x, y } using both explicit ref x, ref y and ergonomic x, y — verify both bind x: &i32, y: &i32.