ref borrows where a pattern would move¶
References › The ref keyword
Level: 201 · working knowledge
One line: ref changes how a binding binds — a borrow instead of a move — and never which values the pattern matches; & in a pattern does the opposite, and since match ergonomics you rarely need to write ref at all.
let maybe_name = Some(String::from("Alice"));
match maybe_name {
Some(ref n) => println!("Hello, {n}"), // Hello, Alice <- n: &String
_ => println!("Hello, world"),
}
println!("Hello again, {}", maybe_name.unwrap_or("world".into())); // Hello again, Alice
This is the second example on std's page for the keyword ↗, with its comments swapped for the output. n borrows the String inside the option, so maybe_name is still whole when the match ends.
Without ref: a partial move¶
std's first example is the same program with Some(n). Its comment says maybe_name "is consumed here"; rustc says what actually happened:
error[E0382]: use of partially moved value: `maybe_name`
--> std_docs_move.rs:9:33
|
5 | Some(n) => println!("Hello, {n}"),
| - value partially moved here
...
9 | println!("Hello again, {}", maybe_name.unwrap_or("world".into()));
| ^^^^^^^^^^ value used here after partial move
|
= note: partial move occurs because value has type `String`, which does not implement the `Copy` trait
help: borrow this binding in the pattern to avoid moving the value
|
5 | Some(ref n) => println!("Hello, {n}"),
| +++
The String moved out of the option, so the option can no longer be used as a whole. The header reads use of because unwrap_or takes self; put println!("{maybe_name:?}") on that line instead and it reads borrow of partially moved value. rustc's help: is the keyword itself.
What ref changes, and what it does not¶
std's docs put it in one line: "Foo(ref foo) matches the same objects as Foo(foo)." ref is a binding mode. It sits on a name, adds one & to that name's type, and plays no part in matching. & is a reference pattern ↗: it demands a reference in the value, matches what is behind it, and binds that.
| Pattern | Scrutinee | Matches | Binds |
|---|---|---|---|
Some(x) |
Option<i32> |
every Some |
x: i32 — a copy (a move, for a non-Copy payload) |
Some(ref x) |
Option<i32> |
every Some — the same values |
x: &i32 — points at the payload |
Some(x) |
Option<&i32> |
every Some |
x: &i32 — the reference itself |
Some(&x) |
Option<&i32> |
every Some |
x: i32 — the reference removed |
Some(&x) |
Option<String> |
— | E0308 |
A 2018 comment under the accepted Stack Overflow answer ↗ calls Some(&v) the "direct opposite of Some(ref v)", and the table agrees: ref adds a reference, & takes one away. They are not two spellings of one thing, and the payload decides whether & even type-checks:
error[E0308]: mismatched types
--> amp_on_owned.rs:4:14
|
3 | match maybe_name {
| ---------- this expression has type `Option<String>`
4 | Some(&n) => println!("Hello, {n}"),
| ^^ expected `String`, found `&_`
|
= note: expected struct `String`
found reference `&_`
help: consider removing `&` from the pattern
|
4 - Some(&n) => println!("Hello, {n}"),
4 + Some(n) => println!("Hello, {n}"),
|
On an Option<&String> it type-checks and is still refused: &n would move the String out from behind a shared reference, E0507. & in a pattern copies out; it cannot move out. Where the & sits has the same rule for let &x = r; and |&n|.
let ref z = x; is let z = &x;¶
let x: u32 = 12;
let ref z = x; // z: &u32
let z2 = &x; // z2: &u32 — the same address, per the run below
println!("{z} {z2}"); // 12 12
A let is a pattern too, so ref works there, and the second line is how to write it. clippy's toplevel_ref_arg (style group, warn by default) flags the first: "ref on an entire let pattern is discouraged, take a reference with & instead".
let ref mut zz = x; is let zz = &mut x;, so x has to be mut. The Stack Overflow answer's 2015 snippet declares let x: u32 = 12; and uses both lines:
error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
--> let_ref_mut.rs:6:9
|
6 | let ref mut zz = x; // zz: &mut u32, points to x
| ^^^^^^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
2 | let mut x: u32 = 12;
| +++
A name binds, and moves, even when you never use it¶
Arm, on an Option<String> |
Binds | maybe_name afterwards |
|---|---|---|
Some(_) |
nothing | whole |
Some(n), n unused |
n: String |
partially moved — E0382, plus an unused variable warning |
Some(_n) |
_n: String |
partially moved — E0382, no warning |
Some(ref _n) |
_n: &String |
whole |
_ is not a name, so nothing is bound and nothing moves. _n is a name; the underscore silences the lint and changes nothing else:
error[E0382]: borrow of partially moved value: `maybe_name`
--> underscore_name_moves.rs:7:16
|
4 | Some(_n) => println!("some"),
| -- value partially moved here
...
7 | println!("{maybe_name:?}");
| ^^^^^^^^^^ value borrowed here after partial move
|
= note: partial move occurs because value has type `String`, which does not implement the `Copy` trait
help: borrow this binding in the pattern to avoid moving the value
|
4 | Some(ref _n) => println!("some"),
| +++
One pattern, two modes¶
ref goes on one name at a time, so a single pattern can move one field and borrow another:
struct Order { id: String, note: String }
let order = Order { id: String::from("A-17"), note: String::from("fragile") };
let Order { id, ref note } = order; // id: String (moved), note: &String
println!("{id} {note}"); // A-17 fragile
println!("{}", order.note); // fragile — it never left the struct
order.note is still in place. order.id is not — E0382, borrow of moved value: order.id — and neither is order as a whole (with #[derive(Debug)] added to print it):
error[E0382]: borrow of partially moved value: `order`
--> partial_move.rs:12:16
|
9 | let Order { id, ref note } = order;
| -- value partially moved here
...
12 | println!("{order:?}"); // the whole struct: refused
| ^^^^^ value borrowed here after partial move
|
= note: partial move occurs because `order.id` has type `String`, which does not implement the `Copy` trait
help: borrow this binding in the pattern to avoid moving the value
|
9 | let Order { ref id, ref note } = order;
| +++
That is ownership tracked per field; ref picks, name by name, which side of it each binding lands on. Matching &order instead cannot do this — every binding under it borrows, so none can move. The kata below is that case.
ref mut: write into the place you matched¶
let mut tally = Some(42);
if let Some(ref mut n) = tally { // n: &mut i32, pointing into tally
*n += 1;
}
println!("{tally:?}"); // Some(43)
The place has to be mut. Without it:
error[E0596]: cannot borrow `tally.0` as mutable, as `tally` is not declared as mutable
--> tally_not_mut.rs:3:17
|
3 | if let Some(ref mut n) = tally {
| ^^^^^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
2 | let mut tally = Some(42);
| +++
if let Some(n) = &mut tally does the same thing without the keyword — match ergonomics reaches ref mut from the scrutinee.
Edition 2024: ref where the borrow is already implied¶
Match a reference with a non-reference pattern and the default binding mode is already ref. Edition 2021 accepts a ref written there anyway, as a redundancy. Edition 2024 refuses it:
let maybe_name = Some(String::from("Alice"));
match &maybe_name {
Some(n) => println!("Hello, {n}"), // Hello, Alice <- n: &String, no `ref` needed
None => println!("Hello, world"),
}
// Some(ref n) in that arm: compiles under --edition 2021, refused under --edition 2024
error: cannot explicitly borrow within an implicitly-borrowing pattern
--> redundant_ref.rs:4:14
|
4 | Some(ref n) => println!("Hello, {n}"),
| ^^^ explicit `ref` binding modifier not allowed when implicitly borrowing
|
= note: for more information, see <https://doc.rust-lang.org/reference/patterns.html#binding-modes>
note: matching on a reference type with a non-reference pattern implicitly borrows the contents
--> redundant_ref.rs:4:9
|
4 | Some(ref n) => println!("Hello, {n}"),
| ^^^^^^^^^^^ this non-reference pattern matches on a reference type `&_`
help: remove the unnecessary binding modifier
|
4 - Some(ref n) => println!("Hello, {n}"),
4 + Some(n) => println!("Hello, {n}"),
|
No error code. The same rule covers & and mut under a borrowing default mode — the three messages, each compiled under both editions:
Under match &value |
--edition 2021 |
--edition 2024 |
|---|---|---|
Some(ref n) (and Some(ref mut n) under &mut value) |
compiles; ref is redundant |
cannot explicitly borrow within an implicitly-borrowing pattern |
Some(&n), value an Option<&i32> |
compiles | cannot explicitly dereference within an implicitly-borrowing pattern |
(mut a, b), value a (i32, i32) |
compiles; mut resets a to by-value, so a: i32 |
cannot mutably bind by value within an implicitly-borrowing pattern |
Two fixes: delete the ref, as rustc's help: says, or make the whole pattern explicit — &Some(ref n) against &maybe_name compiles under 2024, because nothing in it is left to ergonomics. On a 2021 crate, -W rust-2024-compatibility reports all three as the lint rust_2024_incompatible_pat, with a link to the edition guide's Match ergonomics reservations ↗, which says the redundant forms were disallowed "to leave space for other language possibilities".
Where ref is still the natural spelling¶
Most of the time, borrow the scrutinee instead. What is left:
- Moving one field and borrowing another in one pattern. Above, and in the kata.
&on the scrutinee sets every binding at once;refsets one. @on an owned value.whole @ Some(inner)on anOption<String>isE0382, use of partially moved value, and rustc'shelp:writesref whole @ Some(ref inner).match &maybe_name { whole @ Some(inner) => … }compiles too — see Binding with@.- Keeping a temporary alive.
let (ref first, _) = pair();holds the whole tuple open — on Borrowed state. - Taking rustc's suggestion. Every
E0382on this page from a pattern move carries ahelp:that addsref. Applying it is correct; borrowing the scrutinee is the other fix.
clippy has ref_patterns (restriction group, allow by default) for crates that want the keyword gone.
Checkpoint¶
Predict before you open the answer. An explanation of Foo::Bar(x, ref msg), where x is an i32, says "we can still use the borrowed part, but not foo". The same shape as a tuple: after let pair = (5, String::from("hi")); let (count, ref label) = pair; — is pair still usable?
The answer
Verified output of the_ref_keyword.rs — regenerated by tools/run_examples.py, never hand-typed.
1. std's own example: `Some(ref n)` borrows, so `maybe_name` outlives the match
Hello, Alice
Hello again, Alice
2. `ref` changes the binding's type, never which values match
value Some(x) Some(ref x)
Some(3) x: i32 = 3 x: &i32 = 3
None no match no match
3. `&` is the opposite: part of what matches, and it removes a reference
value Some(x) Some(&x)
Some(&3) x: &i32 = 3 x: i32 = 3
None no match no match
4. `let ref z = x;` is `let z = &x;`
let ref z = x; z: &u32 = 12
let z2 = &x; z2: &u32 = 12
same address: true
5. `_` binds nothing, so it moves nothing
Some(_) matched
maybe_name afterwards: Some("Alice")
6. One pattern, two modes: move `id`, borrow `note`
id: alloc::string::String = A-17
note: &alloc::string::String = fragile
order.note is still there: fragile
7. `ref mut` writes through the binding, into the place it matched
Some(ref mut n) = tally -> tally = Some(43)
Some(n) = &mut tally -> tally = Some(44)
8. `ref` on both sides of `@`: the whole and a part, neither moved
whole: &core::option::Option<alloc::string::String> = Some("Alice")
inner: &alloc::string::String = Alice
maybe_name afterwards: Some("Alice")
Checkpoint. After `let (count, ref label) = pair;` is `pair` still usable?
count: i32 = 5
label: &alloc::string::String = hi
pair afterwards: (5, "hi")
Yes. count binds an i32, which is Copy, so binding it by value copies; label borrows. Nothing moved out of pair. The claim holds only when the by-value field is not Copy — the Order case above.
Practice¶
Borrow one field, move the other. Given struct Upload { name: String, bytes: Vec<u8> } and fn store(bytes: Vec<u8>) -> usize, write one let pattern on upload that hands bytes to store and keeps name as a borrow, then print upload.name afterwards. Say whether upload.bytes.len() and let whole = upload; still compile after it, and why let Upload { name, bytes } = &upload; cannot replace your pattern.
Solution
the_ref_keyword_kata.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.
//! Kata solution: one `let` pattern that moves one field and borrows the other.
//!
//! rustc --edition 2024 the_ref_keyword_kata.rs -o /tmp/the_ref_keyword_kata && /tmp/the_ref_keyword_kata
struct Upload {
name: String,
bytes: Vec<u8>,
}
/// Takes the buffer by value: whoever calls this gives it up.
fn store(bytes: Vec<u8>) -> usize {
bytes.len()
}
fn main() {
let upload = Upload { name: String::from("notes.txt"), bytes: vec![7, 8, 9] };
// `bytes` binds by value (moves out), `name` binds by reference (borrows).
let Upload { ref name, bytes } = upload;
let stored = store(bytes);
println!("stored {stored} bytes for {name}");
// `name` never left the struct, so the field is still readable.
println!("upload.name afterwards: {}", upload.name);
// Not these two — each is E0382, because `bytes` moved out of `upload`:
// println!("{}", upload.bytes.len()); // borrow of moved value: `upload.bytes`
// let whole = upload; // use of partially moved value: `upload`
// Why `&upload` cannot do this in one pattern:
// let Upload { name, bytes } = &upload; // bytes: &Vec<u8>, so store(bytes) is E0308
// let &Upload { ref name, bytes } = &upload; // E0507: cannot move out of a shared reference
println!("one pattern, two binding modes: `ref` switches one name, `&upload` switches them all");
}
Verified output of the_ref_keyword_kata.rs — regenerated by tools/run_examples.py, never hand-typed.
If you are coming from another language¶
- C++. Structured bindings make the choice once, for the whole declaration:
auto& [id, note] = o;aliases both members, andauto [id, note] = std::move(o);moves the entire object into one hidden variable. There is no per-name qualifier —auto [id, ¬e] = o;is a syntax error (clang 21: expected identifier). Rust'srefis that missing per-name switch. What changes is the aftermath: after a C++ move,ois still usable, its moved-fromstd::stringmembers in a valid but unspecified state; afterlet Order { id, ref note } = order;rustc refuses to readorder.idor useorderas a whole, and still lets you readorder.note. - Python. Every capture in a
matchis already an alias, never a move:case (items, label):binds names to the very objects inside the tuple —items is pair[0]isTrue, anditems.append(...)changespair. Nothing is ever taken out, so there is noSome(n)versusSome(ref n)to choose between. Rust's default is the opposite — a binding takes the value — andrefis how you ask for an alias. The difference that remains: a Python alias to a mutable object can always write through it, where arefbinding is read-only and writing needsref muton amutplace.
See also¶
- Match ergonomics — default binding modes, and the
refspelling they replaced - Borrowed state — what a
refbinding does to the place it borrows from, including a temporary - Where the
&sits —&xin a pattern removes a reference - Destructuring enums — the variant patterns
refgoes inside - Binding with
@— theref whole @ …form - Ownership and moves — the move a plain binding makes
- Borrowing — the rules a
refbinding obeys once it exists - Reborrowing — rustc suggesting
Some(ref x)for anOption<&mut T> - std: keyword
ref↗ · the Reference: identifier patterns ↗ and binding modes ↗ · RFC 2005, match ergonomics ↗ · edition guide: Match ergonomics reservations ↗
Po polsku¶
ref we wzorcu zmienia sposób wiązania nazwy, a nie to, co wzorzec dopasowuje: zamiast przeniesienia własności (move) dostajesz pożyczenie. Some(n) na Option<String> wyjmuje String z opcji i dalsze użycie maybe_name kończy się E0382 (partially moved); Some(ref n) dopasowuje dokładnie te same wartości, a n ma typ &String. & we wzorcu działa odwrotnie — jest częścią dopasowania, wymaga referencji w wartości i ją zdejmuje, więc Some(&x) pasuje tylko do Option<&T>, a na Option<String> daje E0308.
let ref z = x; to let z = &x; (clippy podpowiada tę drugą formę). Nazwa zaczynająca się od _ nadal wiąże i przenosi — nie przenosi tylko samo _. Jeden wzorzec może jedno pole przenieść, a drugie pożyczyć: let Order { id, ref note } = order; — potem order.note działa, a order.id i całe order już nie. ref mut pozwala pisać w dopasowane miejsce, pod warunkiem że zmienna jest mut.
Od Rusta 1.26 (match ergonomics) zwykle wystarczy pożyczyć samą wartość dopasowywaną (match &maybe_name), a w edycji 2024 ref, & albo mut wpisane tam, gdzie domyślny tryb wiązania już pożycza, to błąd kompilacji (w 2021 — ostrzeżenie rust_2024_incompatible_pat przy -W rust-2024-compatibility). Naturalnym zapisem ref pozostaje wzorzec, który jedno pole przenosi, a drugie pożycza, oraz ref whole @ Some(ref inner) na wartości posiadanej.
Szukaj po polsku: słowo kluczowe ref · wiązanie przez referencję · tryb wiązania we wzorcu · częściowe przeniesienie · rust ref keyword · rust ref vs & · rust cannot explicitly borrow within an implicitly-borrowing pattern