TL;DR
You're holding an immutable reference (&x) while also trying to mutate x via &mut x. Rust won't allow both at once. End the immutable borrow before the mutable one begins. In practice that means restructuring code to avoid overlapping references โ or cloning the data you need upfront.
The error in full
error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
--> src/main.rs:6:5
|
4 | let r = &x; // immutable borrow starts here
5 | println!("{}", r);
6 | x.push(4); // mutable borrow attempted here
| ^^^^^^^^^ mutable borrow occurs here
7 | println!("{}", r); // immutable borrow later used here
| - immutable borrow later used here
The borrow checker enforces one fundamental rule: either multiple immutable references, or one mutable reference โ never both simultaneously. This isn't an arbitrary restriction. It's the mechanism that gives Rust memory safety without a garbage collector.
Root cause
The moment you write let r = &x, Rust considers x immutably borrowed for as long as r is in use. Calling x.push(4) then creates a conflict โ one part of the code holds a read-only view while another tries to change the underlying data. In C++ or Java, this silently produces iterator invalidation or a data race. Rust refuses to compile it.
Borrows last until the last use of the reference โ not until the end of the block. That rule is called Non-Lexical Lifetimes (NLL), stabilized in Rust 2018. It helps a lot, but it can't save you if you actually use the immutable reference after the mutation.
Fix approach 1 โ Stop using the immutable reference before mutating
The most straightforward fix: restructure the code so the immutable borrow ends before the mutation happens.
fn main() {
let mut x = vec![1, 2, 3];
// Use the reference, then let it drop
{
let r = &x;
println!("before: {:?}", r);
} // <-- r goes out of scope here
x.push(4); // now safe to mutate
println!("after: {:?}", x);
}
No extra block needed if you just don't use r after the mutation โ NLL automatically ends the borrow at the last use:
fn main() {
let mut x = vec![1, 2, 3];
let r = &x;
println!("before: {:?}", r);
// r is not used after this line, so the borrow ends here (NLL)
x.push(4);
println!("after: {:?}", x);
}
Fix approach 2 โ Clone the data you need before mutating
Sometimes you genuinely need the old value after the mutation. Clone it upfront โ an owned copy has no borrow on x.
fn main() {
let mut x = vec![1, 2, 3];
let snapshot = x.clone(); // owned copy, no borrow of x
x.push(4);
println!("was: {:?}", snapshot);
println!("now: {:?}", x);
}
Fix approach 3 โ Use indices instead of references
Vec-based code hits E0502 constantly: grab a reference to one element, then try to push another. The compiler blocks it โ even when the push would never touch the element you referenced. Indices sidestep the problem entirely.
// Bad: storing a reference into the vec
fn bad_example(items: &mut Vec<String>) {
let first = &items[0]; // immutable borrow
items.push("new".to_string()); // mutable borrow โ COMPILE ERROR
println!("{}", first);
}
// Good: store the index (or a clone), not the reference
fn good_example(items: &mut Vec<String>) {
let first = items[0].clone(); // owned String, no borrow held
items.push("new".to_string()); // fine
println!("{}", first);
}
Fix approach 4 โ Interior mutability with RefCell (single-threaded)
Graph structures, caches, and event systems sometimes need shared mutable access that the static borrow checker simply can't reason about. RefCell<T> handles this by moving the borrow check from compile time to runtime.
use std::cell::RefCell;
fn main() {
let x = RefCell::new(vec![1, 2, 3]);
let r = x.borrow(); // immutable borrow (runtime)
println!("before: {:?}", *r);
drop(r); // explicitly release before mutating
x.borrow_mut().push(4); // now fine
println!("after: {:?}", x.borrow());
}
One caveat: if two borrows conflict at runtime, RefCell panics rather than causing undefined behaviour. For multi-threaded code, reach for Arc<Mutex<T>> instead.
Fix approach 5 โ Split the struct into separate fields
E0502 inside a struct method usually means Rust can't see that two fields are independent. Borrow fields individually rather than self as a whole.
struct Game {
players: Vec<String>,
log: Vec<String>,
}
impl Game {
fn record_join(&mut self, name: &str) {
// Borrow two separate fields โ this compiles fine
let players = &self.players;
let entry = format!("joined: {}, total: {}", name, players.len());
self.log.push(entry); // mutates log, not players โ OK
}
}
Borrowing &self as a whole and then requesting &mut self looks like a conflict to the compiler, even when the actual fields don't overlap. Reaching directly for self.players and self.log makes the independence explicit.
Verifying the fix
Rust's error messages double as your test suite. Start here:
cargo check
No E0502 means you're clear. Then confirm behaviour hasn't changed:
cargo test
Added a .clone()? Run Clippy too:
cargo clippy
Clippy flags unnecessary clones. On large vecs or strings, a redundant clone can quietly become a serious performance issue.
Quick decision guide
- References don't actually overlap at runtime? โ Restructure so the immutable borrow ends first (approach 1).
- Need a snapshot of the old value? โ Clone it (approach 2).
- Indexing a collection you're also modifying? โ Store the index, not a reference (approach 3).
- Shared ownership or graph structures? โ
RefCellorArc<Mutex>(approach 4). - Conflict inside a struct method? โ Borrow individual fields (approach 5).

