Fix E0386: Attempted to Access Value of Moved or Borrowed Resource
Rust’s ownership system is one of the language’s most powerful features, enabling memory safety without garbage collection. However, this strict ownership model means the compiler will reject code that attempts to use a value after it has been transferred to another owner or loaned out through a reference. Error E0386 specifically signals that you tried to access a value that no longer belongs to the current scope because ownership has moved or a borrow is active.
1. Symptoms
When Rust encounters E0386, the compiler halts with a clear error message indicating the offending line and the nature of the violation. The error manifests differently depending on whether a move or a borrow is involved.
Move After Usage
The compiler reports that a value was used after it was moved, meaning control of the resource was transferred elsewhere and the original binding no longer holds valid data.
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 is moved to s2
println!("{}", s1); // E0386: s1 is no longer valid here
}
---
The compiler output for this scenario looks like:
error[E0386]: borrow of moved value: s1
–> src/main.rs:5:20
|
5 | println!("{}", s1);
| ^^ value moved here
|
= note: move occurs because s1 has type String, which does not implement the Copy trait
### Mutable Borrow Conflict
When a mutable borrow exists, no other references (read or write) are permitted until that borrow ends.
```rust
fn main() {
let mut s = String::from("hello");
let r1 = &mut s; // mutable borrow begins
println!("{}", s); // E0386: cannot access s while borrow is active
}
The compiler rejects this with:
error[E0386]: cannot use `s` because it was mutably borrowed
--> src/main.rs:5:20
|
4 | let r1 = &mut s;
| - borrow later used here
5 | println!("{}", s);
| ^
Scope-Based Borrow Conflicts
Borrow conflicts also occur when a borrow extends beyond the point where the original owner is needed again:
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0]; // immutable borrow begins
v.push(4); // E0386: cannot mutate v while first borrows it
println!("{}", first);
}
2. Root Cause
Rust’s ownership model enforces three fundamental rules at compile time. First, every value has exactly one owner at any given moment. Second, when ownership transfers (a move occurs), the original binding becomes invalid and cannot be accessed. Third, either multiple immutable references OR a single mutable reference may exist at one time, never both simultaneously and never overlapping.
Error E0386 emerges because the Rust borrow checker performs static analysis to track the lifetime and validity of every value at compile time. When you transfer a value to another binding via assignment, function argument passing, or returning a value, the original binding loses access rights. This is not a runtime check—it is enforced by the compiler’s lifetime tracking system. The borrow checker builds a liveness map for each binding, marking when a value becomes unavailable due to a move or when it enters a borrowed state that restricts access.
The underlying mechanism involves Rust’s non-copy types. Types like String, Vec<T>, Box<T>, and other heap-allocated structures implement the Move trait by default. When these values are assigned or passed to functions, ownership transfers and the source binding is logically destroyed. Primitive types with the Copy trait (i32, bool, f64, etc.) behave differently because the compiler automatically duplicates them on assignment, leaving the original binding intact.
Mutable borrows create temporary exclusive access. When you create a &mut reference, the borrow checker enters a mode where the original owner cannot be accessed for the duration of the borrow. This prevents data races at compile time by ensuring no concurrent read and write access to the same memory location.
3. Step-by-Step Fix
The appropriate fix depends on whether you need to preserve the original value, work with a reference, or restructure your code to avoid overlapping borrows.
Fix 1: Clone the Value to Preserve the Original
If you need independent copies of the data, use the clone() method to create a deep copy. This is appropriate when you need to modify or consume the clone independently.
Before:
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1); // E0386
}
After:
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{}", s1); // OK: s1 is still valid
println!("{}", s2);
}
Fix 2: Use a Reference Instead of Taking Ownership
If the function does not need to own the data, pass a reference. This avoids moving the value and keeps the original binding valid.
Before:
fn print_length(s: String) {
println!("{}", s.len());
}
fn main() {
let s = String::from("hello");
print_length(s);
println!("{}", s); // E0386
}
After:
fn print_length(s: &String) {
println!("{}", s.len());
}
fn main() {
let s = String::from("hello");
print_length(&s); // pass a reference
println!("{}", s); // OK: s still owns the data
}
Fix 3: Restructure Code to End Borrow Before Reuse
Move borrow-ending operations before the point where you need the original value again.
Before:
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4); // E0386
println!("{}", first);
}
After:
fn main() {
let mut v = vec![1, 2, 3];
let first_val = v[0]; // copy the value if Copy is implemented
v.push(4); // OK: first_val is not a borrow
println!("{}", first_val);
}
Alternatively, drop the borrow scope explicitly:
fn main() {
let mut v = vec![1, 2, 3];
let first = {
let reference = &v[0];
*reference // release borrow by ending this block
};
v.push(4); // OK: borrow has ended
println!("{}", first);
}
Fix 4: Use Indices Instead of References
If you need to modify a collection while accessing its elements, use usize indices rather than references.
Before:
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4); // E0386
}
After:
fn main() {
let mut v = vec![1, 2, 3];
let first_index = 0;
v.push(4); // OK: first_index is not a borrow
println!("{}", v[first_index]);
}
Fix 5: Restructure to Single Mutable Borrow
When you have multiple mutable references that overlap, refactor the code to create the reference only where needed and drop it immediately.
fn main() {
let mut v = vec![1, 2, 3];
{
let r = &mut v; // borrow begins and ends in this scope
r.push(4);
} // borrow ends
println!("{:?}", v); // OK: can use v now
}
4. Verification
After applying your fix, verify that the code compiles without errors using cargo build or rustc. Create a test case that exercises the ownership patterns you corrected to ensure they behave correctly at runtime as well.
cargo build
A successful build produces no error output. Run your test suite to confirm functional behavior:
cargo test
For the reference-based fix, confirm that the original owner retains access after the function call returns:
fn main() {
let s = String::from("hello");
process(&s);
assert_eq!(s, "hello"); // this assertion should pass
}
fn process(s: &String) {
println!("Processing: {}", s);
}
If you used cloning, confirm that modifications to one binding do not affect the other:
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
s2.push_str(", world");
assert_eq!(s1, "hello"); // s1 is unchanged
assert_eq!(s2, "hello, world"); // s2 has the modification
}
For borrow restructuring, ensure the mutable borrow completes before any subsequent access:
fn main() {
let mut v = vec![1, 2, 3];
{
let r = &mut v;
r.push(4);
} // scope ends and borrow is released
// now safe to use v
v.push(5);
assert_eq!(v, vec![1, 2, 3, 4, 5]);
}
5. Common Pitfalls
Understanding the typical mistakes that lead to E0386 will help you avoid them in the future. The most common pitfall is assuming that Rust behaves like C++ with respect to value assignment. In C++, copying is the default behavior, but in Rust, assignment moves ownership by default for heap-allocated types. This distinction catches many developers new to Rust.
Another frequent mistake involves forgetting that passing a value to a function moves ownership unless the function signature uses a reference type. Developers accustomed to languages where function parameters act as copies often overlook the need to either use references or accept that the original binding becomes invalid.
Nested borrows can also cause confusion. When you create a reference to data inside a borrowed structure, you introduce an additional borrow layer. The compiler tracks the combined lifetimes, and overlapping nested borrows trigger E0386. Be especially careful with iterators that hold references to collection elements—the iterator borrow must end before you can modify the collection.
Cloning everything as a first resort is an anti-pattern that defeats the purpose of Rust’s ownership system. While cloning is sometimes necessary, prefer restructuring your code to work with references and scopes. Excessive cloning introduces unnecessary performance overhead and muddies the ownership model.
Finally, remember that the borrow checker is conservative. It may reject code that is technically safe but cannot be proven safe through static analysis. In such cases, restructuring the code or using lifetime annotations may be required, but fighting against the borrow checker by using unsafe code should be a last resort.
6. Related Errors
E0382: Borrow of Moved Value — This error occurs when a value is borrowed after being moved. Unlike E0386, which focuses on the attempted access, E0382 highlights the borrow attempt on a moved value. The distinction is subtle: E0386 emphasizes the invalid access, while E0382 emphasizes the borrow.
fn main() {
let s = String::from("hello");
let t = s; // move
let r = &t;
println!("{}", s); // E0382
}
E0505: Cannot Move Out of Value Because It Is Already Borrowed — This error prevents moving a value while a borrow of that value is still active. It is the inverse of E0386, occurring when you attempt to move a binding that has active references pointing to it.
fn main() {
let s = String::from("hello");
let r = &s;
let t = s; // E0505: cannot move s while borrowed by r
}
E0594: Cannot Assign to Partially Moved Value — This error arises when you try to mutate a struct or tuple after some of its fields have been moved out. The compiler treats the partially moved value as unavailable until all fields are either moved or the value is consumed.
struct Person { name: String, age: u32 }
fn main() {
let p = Person { name: String::from("Alice"), age: 30 };
let name = p.name; // move the name field
p.age = 31; // E0594: cannot assign to p because name was moved
}
Understanding these related errors builds a complete mental model of Rust’s ownership and borrowing system, enabling you to diagnose and fix E0386 and its cousins efficiently.