Pattern Binding Modes
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
ref explicitly creates a reference binding in a patternref when matching on &Tref mut creates a mutable reference bindingref is still necessary vs when ergonomics handles itOption<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
ref without ergonomics or when ergonomics do not apply; OCaml always binds by reference implicitly.ref mut creates a mutable reference binding — a precise distinction; OCaml uses ref cells for mutable state.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"));
}
}#[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
ref and once relying on match ergonomics — verify they compile to the same behavior.fn append_exclaim(v: &mut Vec<String>) using for s in v.iter_mut() { ... } with ref mut inside the loop to mutate each string.&(String, &str) — verify that (ref a, b) gives a: &String, b: &&str and that ergonomics alone gives a: &String, b: &&str without explicit ref.