Lesson 28 of 94
Fixing Borrow Checker Errors
This lesson is a reference you will come back to. Each section is one error, shown the way you'll meet it in real code, not in a toy example. For each one:
- Symptom: code that looks reasonable and the compiler's actual error.
- Why: what the compiler is protecting you from.
- Fix: the idiomatic repair, which compiles and runs.
The fixes follow a short list of strategies. Once you know them, most borrow errors take seconds to fix:
- Borrow instead of moving:
&xinstead ofx,&strinstead ofString,&[T]instead ofVec<T>. - Shorten a borrow: use the reference earlier, or copy/clone the small value you need out of it.
- Reorder: do the mutation before or after the borrow, not during it.
- Split the borrow: borrow individual fields instead of the whole struct.
- Use indexes instead of long-lived references into collections.
- Collect, then apply: gather changes first, apply them after the loop.
- Move values out of
&mutwithstd::mem::take,replace, orOption::take. - Own the data when a reference would have to outlive its source.
1. "use of moved value" after passing a field to a function (E0382)
Symptom. A function takes a String, so calling it moves the field out of the struct.
struct User {
name: String,
email: String,
}
fn send_welcome(email: String) {
println!("welcome mail sent to {email}");
}
fn main() {
let user = User { name: String::from("Ada"), email: String::from("ada@example.com") };
send_welcome(user.email);
println!("{} <{}>", user.name, user.email);
}error[E0382]: borrow of moved value: `user.email`
--> src/main.rs:13:36
|
12 | send_welcome(user.email);
| ---------- value moved here
13 | println!("{} <{}>", user.name, user.email);
| ^^^^^^^^^^ value borrowed here after move
|
= note: move occurs because `user.email` has type `String`, which does not implement the `Copy` traitWhy. send_welcome takes ownership, and the email is freed when it returns.
Fix. A function that only reads text should take &str.
struct User {
name: String,
email: String,
}
fn send_welcome(email: &str) {
println!("welcome mail sent to {email}");
}
fn main() {
let user = User { name: String::from("Ada"), email: String::from("ada@example.com") };
send_welcome(&user.email);
println!("{} <{}>", user.name, user.email);
}welcome mail sent to ada@example.com
Ada <ada@example.com>2. "borrow of moved value" after looping over a field (E0382)
Symptom. for item in order.items consumes the vector, so the order is partially moved.
struct Order {
id: u32,
items: Vec<String>,
}
fn main() {
let order = Order { id: 7, items: vec![String::from("lamp"), String::from("bulb")] };
for item in order.items {
println!("packing {item}");
}
println!("order {} has {} items", order.id, order.items.len());
}error[E0382]: borrow of moved value: `order.items`
--> src/main.rs:11:49
|
8 | for item in order.items {
| ----------- `order.items` moved due to this implicit call to `.into_iter()`
...
11 | println!("order {} has {} items", order.id, order.items.len());
| ^^^^^^^^^^^ value borrowed here after move
|
note: `into_iter` takes ownership of the receiver `self`, which moves `order.items`
--> /rustc/<version>/library/core/src/iter/traits/collect.rs:310:17
= note: move occurs because `order.items` has type `Vec<String>`, which does not implement the `Copy` trait
help: consider iterating over a slice of the `Vec<String>`'s content to avoid moving into the `for` loop
|
8 | for item in &order.items {
| +Fix. Loop over a reference.
struct Order {
id: u32,
items: Vec<String>,
}
fn main() {
let order = Order { id: 7, items: vec![String::from("lamp"), String::from("bulb")] };
for item in &order.items {
println!("packing {item}");
}
println!("order {} has {} items", order.id, order.items.len());
}packing lamp
packing bulb
order 7 has 2 items3. "cannot borrow as mutable more than once" with two elements (E0499)
Symptom. Two &mut references into the same vector at once.
#[derive(Debug)]
struct Player {
name: &'static str,
gold: u32,
}
fn main() {
let mut players = vec![Player { name: "ana", gold: 50 }, Player { name: "ben", gold: 10 }];
let giver = &mut players[0];
let receiver = &mut players[1];
giver.gold -= 20;
receiver.gold += 20;
println!("{:?}", players);
}error[E0499]: cannot borrow `players` as mutable more than once at a time
--> src/main.rs:10:25
|
9 | let giver = &mut players[0];
| ------- first mutable borrow occurs here
10 | let receiver = &mut players[1];
| ^^^^^^^ second mutable borrow occurs here
11 | giver.gold -= 20;
| ---------------- first borrow later used here
|
= help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slicesWhy. The compiler can't prove that indexes 0 and 1 are different at compile time, and two &mut to the same element would break the aliasing rule.
Fix. get_disjoint_mut checks at runtime that the indexes differ and returns both references. Alternatives are split_at_mut, or modifying one element at a time through the index.
#[derive(Debug)]
struct Player {
name: &'static str,
gold: u32,
}
fn main() {
let mut players = vec![Player { name: "ana", gold: 50 }, Player { name: "ben", gold: 10 }];
if let Ok([giver, receiver]) = players.get_disjoint_mut([0, 1]) {
giver.gold -= 20;
receiver.gold += 20;
}
println!("{:?}", players);
}[Player { name: "ana", gold: 30 }, Player { name: "ben", gold: 30 }]4. "cannot borrow as mutable because it is also borrowed as immutable" while caching a reference (E0502)
Symptom. Keeping a reference to an element, then changing the vector.
fn main() {
let mut queue = vec![String::from("job-1"), String::from("job-2")];
let current = &queue[0];
queue.push(String::from("job-3"));
println!("working on {current}");
}error[E0502]: cannot borrow `queue` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let current = &queue[0];
| ----- immutable borrow occurs here
4 | queue.push(String::from("job-3"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
5 | println!("working on {current}");
| ------- immutable borrow later used hereWhy. push may reallocate the buffer, which would leave current dangling (see Mutable References).
Fix. Reorder so the reference is no longer used, or take an owned copy of what you need.
fn main() {
let mut queue = vec![String::from("job-1"), String::from("job-2")];
let current = queue[0].clone(); // an owned copy, not a borrow
queue.push(String::from("job-3"));
println!("working on {current}, {} queued", queue.len());
}working on job-1, 3 queued5. Calling a &mut self method while iterating over a field (E0499)
Symptom. A very common shape: loop over a field and call another method on self inside the loop.
struct Inventory {
items: Vec<(String, u32)>,
audit: Vec<String>,
}
impl Inventory {
fn record(&mut self, message: String) {
self.audit.push(message);
}
fn restock_empty(&mut self) {
for (name, qty) in &mut self.items {
if *qty == 0 {
*qty = 10;
self.record(format!("restocked {name}"));
}
}
}
}
fn main() {
let mut inv = Inventory { items: vec![(String::from("pen"), 0)], audit: Vec::new() };
inv.restock_empty();
println!("{:?}", inv.audit);
}error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/main.rs:15:17
|
12 | for (name, qty) in &mut self.items {
| ---------------
| |
| first mutable borrow occurs here
| first borrow later used here
...
15 | self.record(format!("restocked {name}"));
| ^^^^ second mutable borrow occurs hereWhy. self.record(...) borrows all of self mutably, including self.items, which the loop is already borrowing. The compiler checks method calls by their signatures, so it can't see that record only touches audit.
Fix. Borrow the fields directly. Field access is visible to the compiler, and different fields can be borrowed at the same time.
struct Inventory {
items: Vec<(String, u32)>,
audit: Vec<String>,
}
impl Inventory {
fn restock_empty(&mut self) {
for (name, qty) in &mut self.items {
if *qty == 0 {
*qty = 10;
self.audit.push(format!("restocked {name}")); // borrows only `audit`
}
}
}
}
fn main() {
let mut inv = Inventory { items: vec![(String::from("pen"), 0), (String::from("ink"), 4)], audit: Vec::new() };
inv.restock_empty();
println!("{:?} {:?}", inv.items, inv.audit);
}[("pen", 10), ("ink", 4)] ["restocked pen"]Another good fix is to collect the messages in a local Vec during the loop and call self.record for each one after it.
6. A getter keeps all of self borrowed (E0506)
Symptom. A getter returns a reference, and then another field is updated.
struct Session {
user: String,
requests: u32,
}
impl Session {
fn user(&self) -> &str {
&self.user
}
fn handle(&mut self) {
let who = self.user();
self.requests += 1;
println!("request #{} from {who}", self.requests);
}
}
fn main() {
let mut s = Session { user: String::from("ada"), requests: 0 };
s.handle();
}error[E0506]: cannot assign to `self.requests` because it is borrowed
--> src/main.rs:13:9
|
12 | let who = self.user();
| ---- `self.requests` is borrowed here
13 | self.requests += 1;
| ^^^^^^^^^^^^^^^^^^ `self.requests` is assigned to here but it was already borrowed
14 | println!("request #{} from {who}", self.requests);
| --- borrow later used hereWhy. self.user() returns a reference tied to all of self (elision rule 3), so self stays borrowed while who is alive.
Fix. Inside the type's own methods, use the field directly, which borrows only that field. Or finish the mutation before taking the reference.
struct Session {
user: String,
requests: u32,
}
impl Session {
fn handle(&mut self) {
let who = &self.user; // borrows only the `user` field
self.requests += 1;
println!("request #{} from {who}", self.requests);
}
}
fn main() {
let mut s = Session { user: String::from("ada"), requests: 0 };
s.handle();
s.handle();
}request #1 from ada
request #2 from ada7. "cannot move out of ... because it is borrowed" (E0505)
Symptom. Handing a value to a new owner while a reference to it is still in use.
fn archive(log: Vec<String>) -> usize {
log.len()
}
fn main() {
let log = vec![String::from("start"), String::from("stop")];
let last = &log[log.len() - 1];
let archived = archive(log);
println!("archived {archived} lines, last was {last}");
}error[E0505]: cannot move out of `log` because it is borrowed
--> src/main.rs:8:28
|
6 | let log = vec![String::from("start"), String::from("stop")];
| --- binding `log` declared here
7 | let last = &log[log.len() - 1];
| --- borrow of `log` occurs here
8 | let archived = archive(log);
| ^^^ move out of `log` occurs here
9 | println!("archived {archived} lines, last was {last}");
| ---- borrow later used here
|
help: consider cloning the value if the performance cost is acceptable
|
7 | let last = &log.clone()[log.len() - 1];
| ++++++++Fix. Use the borrow before the move, or copy out what you need.
fn archive(log: Vec<String>) -> usize {
log.len()
}
fn main() {
let log = vec![String::from("start"), String::from("stop")];
let last = log[log.len() - 1].clone();
let archived = archive(log);
println!("archived {archived} lines, last was {last}");
}archived 2 lines, last was stop8. "cannot assign to ... because it is borrowed" (E0506)
Symptom. Replacing a value while a reference into it is still used.
fn main() {
let mut config = String::from("mode=dev");
let mode = &config[5..];
config = String::from("mode=prod");
println!("started in {mode}");
}error[E0506]: cannot assign to `config` because it is borrowed
--> src/main.rs:4:5
|
3 | let mode = &config[5..];
| ------ `config` is borrowed here
4 | config = String::from("mode=prod");
| ^^^^^^ `config` is assigned to here but it was already borrowed
5 | println!("started in {mode}");
| ---- borrow later used hereWhy. Assigning a new value drops the old one, and mode points into the old one.
Fix. Finish using the reference first, or keep an owned copy.
fn main() {
let mut config = String::from("mode=dev");
let mode = config[5..].to_string();
config = String::from("mode=prod");
println!("started in {mode}, now {config}");
}started in dev, now mode=prod9. "cannot move out of self.x which is behind a shared reference" (E0507)
Symptom. A getter that returns a field by value.
struct Account {
owner: String,
}
impl Account {
fn owner(&self) -> String {
self.owner
}
}
fn main() {
let a = Account { owner: String::from("Grace") };
println!("{}", a.owner());
}error[E0507]: cannot move out of `self.owner` which is behind a shared reference
--> src/main.rs:7:9
|
7 | self.owner
| ^^^^^^^^^^ move occurs because `self.owner` has type `String`, which does not implement the `Copy` trait
|
help: consider cloning the value if the performance cost is acceptable
|
7 | self.owner.clone()
| ++++++++Why. &self only borrows the account, and you can't move something out of a borrow: the account would be left with no owner string.
Fix. Return a reference, or a clone if the caller really needs its own copy.
struct Account {
owner: String,
}
impl Account {
fn owner(&self) -> &str {
&self.owner
}
fn owner_cloned(&self) -> String {
self.owner.clone()
}
}
fn main() {
let a = Account { owner: String::from("Grace") };
println!("{} / {}", a.owner(), a.owner_cloned());
}Grace / Grace10. Moving an Option field out of &mut self (E0507)
Symptom. Consuming an optional field in a method that only has &mut self.
struct Upload {
pending_chunk: Option<Vec<u8>>,
sent_bytes: usize,
}
fn send(chunk: Vec<u8>) -> usize {
chunk.len()
}
impl Upload {
fn flush(&mut self) {
if let Some(chunk) = self.pending_chunk {
self.sent_bytes += send(chunk);
}
}
}
fn main() {
let mut u = Upload { pending_chunk: Some(vec![1, 2, 3]), sent_bytes: 0 };
u.flush();
}error[E0507]: cannot move out of `self.pending_chunk` as enum variant `Some` which is behind a mutable reference
--> src/main.rs:12:30
|
12 | if let Some(chunk) = self.pending_chunk {
| ----- ^^^^^^^^^^^^^^^^^^
| |
| data moved here
| move occurs because `chunk` has type `Vec<u8>`, which does not implement the `Copy` trait
|
help: consider borrowing here
|
12 | if let Some(chunk) = &self.pending_chunk {
| +Fix. Option::take moves the value out and leaves None, which is exactly the state you want after sending.
struct Upload {
pending_chunk: Option<Vec<u8>>,
sent_bytes: usize,
}
fn send(chunk: Vec<u8>) -> usize {
chunk.len()
}
impl Upload {
fn flush(&mut self) {
if let Some(chunk) = self.pending_chunk.take() {
self.sent_bytes += send(chunk);
}
}
}
fn main() {
let mut u = Upload { pending_chunk: Some(vec![1, 2, 3]), sent_bytes: 0 };
u.flush();
u.flush(); // nothing pending the second time
println!("sent {} bytes, pending: {:?}", u.sent_bytes, u.pending_chunk);
}sent 3 bytes, pending: NoneFor non-Option fields, std::mem::take(&mut self.buffer) does the same, leaving an empty value behind.
11. "cannot return reference to local variable" (E0515)
Symptom. Building a value inside a function and returning a reference to it.
fn display_name(user_id: &str) -> &str {
let name = format!("user-{}", user_id.to_uppercase());
&name
}
fn main() {
println!("{}", display_name("a7f3"));
}error[E0515]: cannot return reference to local variable `name`
--> src/main.rs:3:5
|
3 | &name
| ^^^^^ returns a reference to data owned by the current functionFix. Return the owned value. There is nothing to borrow from once the function returns.
fn display_name(user_id: &str) -> String {
format!("user-{}", user_id.to_uppercase())
}
fn main() {
println!("{}", display_name("a7f3"));
}user-A7F312. "does not live long enough" when collecting references (E0597)
Symptom. Pushing references to values created inside a loop into a vector that lives outside it.
fn main() {
let raw = "alpha,BETA,Gamma";
let mut normalized: Vec<&str> = Vec::new();
for part in raw.split(',') {
let lower = part.to_lowercase();
normalized.push(&lower);
}
println!("{:?}", normalized);
}error[E0597]: `lower` does not live long enough
--> src/main.rs:6:25
|
3 | let mut normalized: Vec<&str> = Vec::new();
| -------------- variable `normalized` declared here
4 | for part in raw.split(',') {
5 | let lower = part.to_lowercase();
| ----- binding `lower` declared here
6 | normalized.push(&lower);
| ---------- ^^^^^^ borrowed value does not live long enough
| |
| borrow later used here
7 | }
| - `lower` dropped here while still borrowed
|
= note: `normalized` is a collection that stores borrowed references, but `lower` does not live long enough to be stored in it
= help: buffer reuse with borrowed references requires unsafe code or restructuringWhy. Each lower is dropped at the end of its iteration, while the vector keeps the reference.
Fix. Store owned values.
fn main() {
let raw = "alpha,BETA,Gamma";
let mut normalized: Vec<String> = Vec::new();
for part in raw.split(',') {
normalized.push(part.to_lowercase());
}
println!("{:?}", normalized);
}["alpha", "beta", "gamma"]13. "temporary value dropped while borrowed" (E0716)
Symptom. Borrowing from a value that was never stored in a variable.
fn read_line() -> String {
String::from("id,name,email")
}
fn main() {
let columns: Vec<&str> = read_line().split(',').collect();
println!("{:?}", columns);
}error[E0716]: temporary value dropped while borrowed
--> src/main.rs:6:30
|
6 | let columns: Vec<&str> = read_line().split(',').collect();
| ^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
7 | println!("{:?}", columns);
| ------- borrow later used here
|
help: consider using a `let` binding to create a longer lived value
|
6 ~ let binding = read_line();
7 ~ let columns: Vec<&str> = binding.split(',').collect();
|Fix. Bind the temporary to a variable so it lives as long as the references into it.
fn read_line() -> String {
String::from("id,name,email")
}
fn main() {
let line = read_line();
let columns: Vec<&str> = line.split(',').collect();
println!("{:?}", columns);
}["id", "name", "email"]14. "cannot borrow ... as mutable, as it is behind a & reference" (E0596)
Symptom. A function signature that says "read-only" while the body tries to write.
fn add_default_role(roles: &Vec<String>) {
if roles.is_empty() {
roles.push(String::from("viewer"));
}
}
fn main() {
let mut roles = Vec::new();
add_default_role(&roles);
println!("{:?}", roles);
}error[E0596]: cannot borrow `*roles` as mutable, as it is behind a `&` reference
--> src/main.rs:3:9
|
3 | roles.push(String::from("viewer"));
| ^^^^^ `roles` is a `&` reference, so it cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
1 | fn add_default_role(roles: &mut Vec<String>) {
| +++Fix. Ask for what the function needs: &mut.
fn add_default_role(roles: &mut Vec<String>) {
if roles.is_empty() {
roles.push(String::from("viewer"));
}
}
fn main() {
let mut roles = Vec::new();
add_default_role(&mut roles);
add_default_role(&mut roles);
println!("{:?}", roles);
}["viewer"]15. Changing self inside a match on one of its fields (E0502)
Symptom. Matching on a borrowed state field and calling a &mut self method in an arm.
#[derive(Debug)]
enum State {
Idle,
Running { job: String },
}
struct Worker {
state: State,
completed: Vec<String>,
}
impl Worker {
fn finish(&mut self, job: &str) {
self.completed.push(job.to_string());
self.state = State::Idle;
}
fn tick(&mut self) {
match &self.state {
State::Running { job } => self.finish(job),
State::Idle => {}
}
}
}
fn main() {
let mut w = Worker { state: State::Running { job: String::from("backup") }, completed: Vec::new() };
w.tick();
}error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:20:39
|
19 | match &self.state {
| ----------- immutable borrow occurs here
20 | State::Running { job } => self.finish(job),
| ^^^^^------^^^^^
| | |
| | immutable borrow later used by call
| mutable borrow occurs hereWhy. job borrows from self.state, and finish wants all of self, including the state it would overwrite while job still points into it.
Fix. Take the old state out with std::mem::replace, so job is owned, then decide on the new state.
#[derive(Debug)]
enum State {
Idle,
Running { job: String },
}
struct Worker {
state: State,
completed: Vec<String>,
}
impl Worker {
fn tick(&mut self) {
match std::mem::replace(&mut self.state, State::Idle) {
State::Running { job } => self.completed.push(job), // `job` is owned now
State::Idle => {}
}
}
}
fn main() {
let mut w = Worker { state: State::Running { job: String::from("backup") }, completed: Vec::new() };
w.tick();
w.tick();
println!("{:?} {:?}", w.state, w.completed);
}Idle ["backup"]This "take the state out, compute the next state, put it back" pattern is how most state machines are written in Rust.
16. Returning a reference from inside a loop, then mutating (a known limitation)
Symptom. "Find it, or insert it" code that is actually safe, but is rejected by the current borrow checker.
fn get_or_insert<'a>(names: &'a mut Vec<String>, name: &str) -> &'a mut String {
for n in names.iter_mut() {
if n == name {
return n;
}
}
names.push(name.to_string());
names.last_mut().unwrap()
}
fn main() {
let mut names = vec![String::from("ana")];
get_or_insert(&mut names, "ben").push('!');
println!("{:?}", names);
}error[E0499]: cannot borrow `*names` as mutable more than once at a time
--> src/main.rs:7:5
|
1 | fn get_or_insert<'a>(names: &'a mut Vec<String>, name: &str) -> &'a mut String {
| -- lifetime `'a` defined here
2 | for n in names.iter_mut() {
| ----- first mutable borrow occurs here
3 | if n == name {
4 | return n;
| - returning this value requires that `*names` is borrowed for `'a`
...
7 | names.push(name.to_string());
| ^^^^^ second mutable borrow occurs hereWhy. The borrow for the early return is considered to last until the end of the function, even on the path where the loop finishes without returning. This is a known limitation (called "NLL problem case #3"). A future borrow checker, Polonius, will accept this code.
Fix. Find the index first, then create the one &mut you return.
fn get_or_insert<'a>(names: &'a mut Vec<String>, name: &str) -> &'a mut String {
let index = match names.iter().position(|n| n == name) {
Some(i) => i,
None => {
names.push(name.to_string());
names.len() - 1
}
};
&mut names[index]
}
fn main() {
let mut names = vec![String::from("ana")];
get_or_insert(&mut names, "ben").push('!');
get_or_insert(&mut names, "ana").push('?');
println!("{:?}", names);
}["ana?", "ben!"]The HashMap entry API, covered in HashMap, solves the same problem for maps in one call.
A checklist for any borrow error
- Read the whole message. It names the borrow, where it starts, and where it is later used. The
help:line is often the exact fix. - Ask who owns the data. If a reference must outlive its source, the design needs an owned value.
- Look for a borrow that lasts longer than needed. Move the last use earlier, or copy out the small value you need.
- Look for a method call that borrows all of
self. Borrow individual fields instead. - Look for "a reference into a collection + a change to the collection". Use an index, or collect changes and apply them later.
- Use
take,replace, andOption::taketo move out of&mut. - Clone as a last resort, and remember it is a real, visible cost.
This completes the Lifetimes section. Lifetime bounds, variance, higher-ranked trait bounds, and lifetimes in impl Trait, dyn Trait, and generic associated types are covered in Advanced Lifetimes, the first lesson of Advanced Rust, because they build on traits, closures, and trait objects.
Next is Traits.
