Pattern matching: ref creates, & consumes
You're writing a function that takes a Config struct. You need to read the name field to print a log message, but you also need to consume the timeout field to start a timer. If you move the whole struct, you can't read the name later. If you clone the struct, you waste memory. You need to borrow the name and move the timeout. Pattern matching gives you a way to do both at once, but the syntax trips up everyone at first.
The confusion comes from ref and &. They look similar, but they do opposite things. One wraps a value in a reference. The other unwraps a reference to get the value inside. Getting this wrong leads to type errors that make no sense until you see the pattern.
The core difference
ref creates a new reference to a part of the value. & matches an existing reference and binds the value it points to.
Think of & as a request to open a package. You hand the pattern a box (&T), and & says "open this box, I want the contents." Think of ref as a request to tag a package. You hand the pattern a loose item (T), and ref says "put a tag on this item, I want a pointer to it."
& is a matching operator. It checks the shape of the data. ref is a binding modifier. It changes the type of the variable you're creating.
Minimal examples
Here is the simplest case. You have a value. You want a reference to it. Or you have a reference. You want the value inside.
fn main() {
let data = String::from("hello");
// `ref` creates a reference to `data`.
// `r` is `&String`. `data` stays valid and untouched.
let ref r = data;
// `&` matches a reference.
// The value on the right is `&data`.
// The pattern `&s` says "match a reference, bind `s` to the inner value."
// `s` is `String`. This moves the String out of `data`.
// `data` is now invalid.
let &s = &data;
}
The community almost never writes let ref x = y. You just write let x = &y. It's cleaner and more explicit. ref shines inside complex patterns where you can't put the & on the left side easily.
fn main() {
let data = String::from("hello");
// Preferred style for simple bindings.
// `x` is `&String`.
let x = &data;
// `ref` is rarely used here.
// It works, but `let x = &data` is the convention.
let ref y = data;
}
Treat ref as a binding modifier, not a pattern. It changes what the variable holds, not what you're matching.
The two phases of pattern matching
Rust patterns work in two steps. First, Rust checks if the value matches the shape. Second, Rust binds variables to parts of the value.
& lives in the first phase. It says "I expect a reference here." If the value is not a reference, the match fails. ref lives in the second phase. It says "Bind this variable as a reference." It doesn't care about the shape. It just changes the binding.
This distinction matters when you nest patterns. You can chain & to match multiple layers of references. You cannot chain ref. ref is a keyword that applies to a single binding.
fn main() {
let val = 5;
// `&&` matches two layers of references.
// The value is `&&5`.
// The pattern unwraps both layers.
// `x` is `i32`.
let &&x = &&val;
// `ref` applies to the binding `y`.
// `y` is `&i32`.
// You cannot write `let ref ref y = val`.
// `ref` is not an operator. It's a modifier.
let ref y = val;
}
In a match arm, the distinction is even sharper. Some(&x) matches a Some that holds a reference. Some(ref x) matches a Some that holds a value and gives you a reference to it.
fn main() {
// Case 1: The option holds a value.
let opt = Some(5);
match opt {
// `ref x` creates a reference to the inner value.
// `x` is `&i32`.
// This avoids moving the value out of the Option.
Some(ref x) => println!("Got {}", x),
None => {}
}
// Case 2: The option holds a reference.
let opt_ref = Some(&5);
match opt_ref {
// `&x` matches the reference inside the Option.
// `x` is `i32`.
// This moves or copies the value out of the reference.
Some(&x) => println!("Got {}", x),
None => {}
}
}
Remember the two phases. & shapes the match. ref shapes the binding.
Realistic usage: borrowing inside destructuring
The most common use for ref is destructuring a struct or enum where you need to borrow some fields and move others. This saves you from cloning data you only need to read.
struct Config {
name: String,
timeout: u32,
retries: u32,
}
/// Process a config by borrowing the name and consuming the settings.
fn process(config: Config) {
// We want to log the name without moving it.
// We want to consume timeout and retries.
// `ref name` binds `name` as `&String`.
// `timeout` and `retries` are moved out as `u32`.
let Config { ref name, timeout, retries } = config;
// `name` is a reference. We can read it.
println!("Processing config: {}", name);
// `timeout` and `retries` are owned values.
println!("Timeout: {}, Retries: {}", timeout, retries);
// `config.name` is still valid because we only borrowed it.
// `config.timeout` and `config.retries` were moved out.
}
This pattern appears in parsers, state machines, and UI code. You have a large structure. You need to pass a reference to one part to a helper function, but you need to own another part to store it. ref lets you split the ownership precisely.
Don't clone to borrow. Use ref.
Pitfalls and compiler errors
The most common error is using & when the value is not a reference. You try to unwrap something that isn't wrapped.
fn main() {
let val = 5;
// Error: expected reference, found integer.
// `&` expects a reference on the right side.
// `val` is an `i32`, not `&i32`.
let &x = val;
}
The compiler rejects this with E0308 (mismatched types). It tells you it expected a reference but found an integer. The fix is to either provide a reference or remove the & from the pattern.
fn main() {
let val = 5;
// Fix 1: Provide a reference.
let &x = &val;
// Fix 2: Remove the `&` if you just want the value.
let x = val;
}
Another pitfall is confusing ref with & in nested patterns. If you have a &Option<T>, you might try to use ref to get the inner value. That doesn't work. ref creates a reference to the whole thing. You need & to match the outer reference, then ref or & for the inner part.
fn main() {
let opt = Some(5);
let ref_to_opt = &opt;
// `ref x` binds `x` to `&ref_to_opt`.
// `x` is `&&Option<i32>`.
// This is rarely what you want.
let ref x = ref_to_opt;
// To get the inner value, match the reference first.
// `&Some(ref y)` matches the reference, then the Some,
// then binds `y` as a reference to the inner value.
match ref_to_opt {
&Some(ref y) => println!("Inner value: {}", y),
_ => {}
}
}
If the compiler rejects your &, check the type. You can't unwrap what isn't wrapped.
Decision: when to use what
Use & in a pattern when the value you are matching is already a reference and you want to bind the inner value. Use & to dereference during destructuring, like let &x = &val to get x as the owned value. Use & in match arms to match options or results that hold references, like Some(&x).
Use ref in a pattern when you have an owned value and you want to bind a reference to it without moving. Use ref inside struct or enum patterns to borrow a field while moving others, like let Struct { ref field } = val. Use ref in match arms to avoid cloning large payloads, like Some(ref payload).
Reach for let x = &val instead of let ref x = val for simple bindings. The explicit reference syntax is clearer and preferred by the community. Reserve ref for complex patterns where the binding modifier is necessary.
Use ref mut when you need a mutable reference inside a pattern. The logic mirrors ref, but the binding allows mutation. Use ref mut when you need to modify a field while destructuring, like let Struct { ref mut counter } = val.
Trust the borrow checker. It usually has a point. If you're fighting ref, you probably want let x = &y.