Borrow checker errors in Rust usually mean your code is accessing memory in an unsafe or conflicting way. These errors often look cryptic at first, but they are actually protecting you from common bugs like use-after-free, data races, and memory leaks.
When I started learning Rust, the borrow checker felt like a strict teacher that would not let me do anything. But over time, I began to understand why it was so picky. It taught me to think clearly about ownership and lifetimes, and now I rely on it to catch mistakes before I ever run my code.
This article will explain common borrow checker errors, why they happen, and how to fix them with clear, beginner-friendly examples.
Why the Borrow Checker Exists
Rust’s borrow checker enforces these core rules:
- Only one mutable reference at a time, or
- Any number of immutable references, but not both
- A reference must never outlive the value it points to
These rules ensure memory safety without needing a garbage collector.
Common Error 1: Mutable and Immutable References at the Same Time
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &mut s; // error
println!("{}, {}, {}", r1, r2, r3);
}
Why this fails: Rust does not allow a mutable reference when immutable ones are still active.
How to fix: Use the mutable reference after the immutable ones are no longer used.
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2); // use them first
let r3 = &mut s; // now okay
println!("{}", r3);
}
Common Error 2: Use After Move
fn main() {
let s = String::from("hello");
takes_ownership(s);
println!("{}", s); // error
}
fn takes_ownership(s: String) {
println!("{}", s);
}
Why this fails: Ownership of s was moved to the function.
How to fix: Use a reference instead.
fn takes_ownership(s: &String) {
println!("{}", s);
}
Or return the value back if you need to reuse it.
Common Error 3: Dangling References
fn dangle() -> &String {
let s = String::from("hello");
&s // error
}
Why this fails: s goes out of scope and is dropped, so the reference becomes invalid.
How to fix: Return the owned value instead.
fn no_dangle() -> String {
String::from("hello")
}
Common Error 4: Lifetime Does Not Live Long Enough
fn longest(x: &String, y: &String) -> &String {
if x.len() > y.len() {
x
} else {
y
}
}
Why this fails: Rust cannot guarantee the returned reference is valid for the caller.
How to fix: Add explicit lifetimes.
We cover this in detail in the Lifetimes in Rust article.
Tips for Fixing Borrow Checker Errors
- Read the error message closely. It usually explains what is happening.
- Use smaller scopes to shorten lifetimes.
- Clone values if needed, but be aware of performance.
- Split logic into multiple functions to isolate ownership.
- Use Rust Playground to experiment with variations.
Real-World Example: Mutable Borrow Conflict
fn main() {
let mut v = vec![1, 2, 3];
for i in &v {
v.push(*i * 2); // cannot borrow mutably while iterating immutably
}
}
How to fix: Collect into a new vector.
fn main() {
let v = vec![1, 2, 3];
let mut new_v = v.clone();
for i in &v {
new_v.push(*i * 2);
}
println!("{:?}", new_v);
}
Summary
The borrow checker might seem difficult at first, but it is your ally. It enforces rules that eliminate common bugs in other languages. The more you understand it, the more confident and safe your Rust code becomes.
Rust Intermediate Concepts
- Generic Types in Functions and Structs
- Implementing Traits for Custom Behavior in Rust
- Trait Bounds and Constraints in Rust
- Lifetimes in Rust
- Using Closures (Anonymous Functions) in Rust
- The Iterator Trait and .next() in Rust
- Higher Order Iterator Methods: map, filter, and fold in Rust
- Using impl Trait for Simplicity in Rust
- Advanced Collections: HashSet and BTreeMap in Rust
- Custom Error Types and the Error Trait in Rust
- Option and Result Combinators in Rust
- Writing Unit Tests in Rust
- Integration Testing in Rust
- Logging in Rust with the log Crate
- Cargo Tips and Tricks for Rust Projects
- CLI Argument Parsing with Clap in Rust
- File I/O and File System Operations in Rust
- Running External Commands in Rust
- Make HTTP Requests in Rust with Reqwest
- JSON Serialization and Deserialization in Rust with Serde
- Building a Weather CLI in Rust
- Date and Time Handling in Rust with Chrono
- Using Regular Expressions in Rust with the regex Crate
- Memory Management in Rust
- Understanding Borrow Checker Errors in Rust
- Interacting with Databases in Rust
- Building a Todo List CLI in Rust with SQLite
- Concurrency in Rust