1. Symptoms
When attempting to compile Rust code that returns a mutable reference to self from a method that takes an immutable &self parameter, the compiler emits error E0384.
Typical error messages you’ll encounter:
error[E0384]: cannot borrow `self` as mutable because it is also borrowed as immutable
--> src/main.rs:10:9
|
9 | fn get_mut(&self) -> &mut Self {
| ----- first immutable borrow occurs here
10 | &mut *self
| ^^^^^^^^^ second borrow occurs here due to use of `self` in the return type
|
= note: this error occurs in the generation of the v-table for trait implementation
= note: the old error code E0501 has been superseded
---
```text
error[E0384]: cannot borrow `self` as mutable because it is also borrowed as immutable
--> src/lib.rs:15:13
|
14 | fn push(&mut self, elem: T) -> &mut T {
| --------- first borrow occurs due to use of `self` in the return type
15 | let elem_ref = &mut self.data[self.len];
| ^^^^^^^^^^^^^^^^^^^^^ second borrow occurs here
The error specifically states that you cannot borrow self as mutable because it is already borrowed as immutable. This is a fundamental violation of Rust’s borrowing rules, which enforce that you cannot have both an immutable and mutable reference to the same data at the same time.
Additional symptoms include:
- Methods that return
&mut Selffrom&selfimplementations - Attempting to get mutable access to a field while returning a reference tied to
&self - Nested method calls where the outer method holds an immutable borrow while calling a method expecting mutable access
2. Root Cause
The root cause of error E0384 is a lifetime/borrowing conflict where the compiler detects two overlapping borrows of self:
- The first borrow: The implicit immutable borrow of
selffrom the method receiver (&self) - The second borrow: The explicit mutable borrow attempted via
&mut selfor&mut *selfin the return type
Rust’s borrowing rules state that:
- You may have either one mutable reference or any number of immutable references
- You cannot have both simultaneously
- A mutable reference excludes all other references
Consider this problematic pattern:
struct Container {
data: Vec<i32>,
}
impl Container {
// This method signature itself creates the conflict
fn get_mut(&self) -> &mut Vec<i32> {
&mut self.data // Compiler sees: immutable borrow `&self` + mutable borrow `&mut self.data`
}
}
The method signature fn get_mut(&self) already creates an immutable borrow of self. When you then try to return &mut self.data, you’re attempting to create a mutable borrow that would coexist with the existing immutable borrow. This violates Rust’s aliasing rules.
Another common scenario involves method chaining:
impl Widget {
fn update(&self) -> &mut Widget {
self.inner = Inner::new(); // Cannot mutably borrow `self.inner` while `&self` is active
&mut *self
}
}
Here, &self creates an immutable borrow, and trying to access self.inner mutably (or return &mut *self) creates a conflicting mutable borrow.
3. Step-by-Step Fix
Fix 1: Change the method signature to accept &mut self
If you need to return a mutable reference to the object, the method must take a mutable receiver.
Before:
struct Counter {
count: u32,
}
impl Counter {
fn get_mut_ref(&self) -> &mut u32 {
&mut self.count
}
}
fn main() {
let mut counter = Counter { count: 0 };
let ref_to_count = counter.get_mut_ref();
println!("{}", ref_to_count);
}
After:
struct Counter {
count: u32,
}
impl Counter {
fn get_mut_ref(&mut self) -> &mut u32 {
&mut self.count
}
}
fn main() {
let mut counter = Counter { count: 0 };
let ref_to_count = counter.get_mut_ref();
println!("{}", ref_to_count);
}
Now the method takes &mut self, establishing a mutable borrow that can coexist with the mutable reference return type.
Fix 2: Return owned data instead of a reference
When you need to modify and return something, consider returning owned data rather than a reference.
Before:
struct Parser {
buffer: String,
}
impl Parser {
fn parse_header(&self) -> &mut String {
&mut self.buffer
}
}
After:
struct Parser {
buffer: String,
}
impl Parser {
fn parse_header(&mut self) -> String {
// Return owned String, caller decides what to do with it
let result = self.buffer.split_whitespace().next().unwrap_or("").to_string();
result
}
}
Fix 3: Use interior mutability with RefCell
For cases where you need shared ownership with mutation, use RefCell to move borrow checking to runtime.
Before:
struct State {
value: i32,
}
impl State {
fn modify(&self) -> &mut i32 {
&mut self.value // E0384
}
}
After:
use std::cell::RefCell;
struct State {
value: RefCell<i32>,
}
impl State {
fn modify(&self) -> &mut i32 {
self.value.borrow_mut()
}
}
fn main() {
let state = State { value: RefCell::new(42) };
let result = state.modify();
*result = 100;
println!("{}", state.value.borrow());
}
⚠️ Unverified: Note that RefCell panics at runtime if you attempt nested mutable borrows. Ensure your usage pattern doesn’t create conflicting borrows.
Fix 4: Restructure the method to avoid returning &mut self
Before:
struct Buffer {
data: Vec<u8>,
}
impl Buffer {
fn flush(&self) -> &mut Buffer {
// Process data...
&mut *self
}
}
After:
struct Buffer {
data: Vec<u8>,
}
impl Buffer {
fn flush(&mut self) -> &mut Buffer {
// Process data...
self
}
}
fn main() {
let mut buffer = Buffer { data: vec![1, 2, 3] };
let buffer_ref = buffer.flush();
println!("Flushed buffer with {} bytes", buffer_ref.data.len());
}
Fix 5: Split the operation into separate methods
Before:
struct Graph {
nodes: Vec<Node>,
}
impl Graph {
fn add_and_get(&self, node: Node) -> &mut Node {
self.nodes.push(node);
&mut self.nodes.last_mut().unwrap()
}
}
After:
struct Graph {
nodes: Vec<Node>,
}
impl Graph {
fn add_node(&mut self, node: Node) {
self.nodes.push(node);
}
fn get_last_node_mut(&mut self) -> Option<&mut Node> {
self.nodes.last_mut()
}
}
fn main() {
let mut graph = Graph { nodes: Vec::new() };
graph.add_node(Node { id: 1 });
if let Some(node) = graph.get_last_node_mut() {
println!("Last node ID: {}", node.id);
}
}
4. Verification
After applying one of the fixes above, verify the error is resolved by compiling your project:
cargo build
A successful build produces:
Compiling my_project v0.1.0 (file:///path/to/my_project)
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
For the RefCell approach, add a test to verify runtime borrow checking:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_interior_mutability() {
let state = State { value: RefCell::new(10) };
// First mutable borrow
let first = state.modify();
*first = 20;
// Second mutable borrow will panic at runtime
// Uncomment to verify:
// let second = state.modify();
// *second = 30;
assert_eq!(*state.value.borrow(), 20);
}
#[test]
fn test_proper_mutable_receiver() {
let mut counter = Counter { count: 5 };
let ref_to_count = counter.get_mut_ref();
*ref_to_count = 10;
assert_eq!(counter.count, 10);
}
}
Run the tests with:
cargo test
Expected output:
running 2 tests
test tests::test_interior_mutability ... ok
test tests::test_proper_mutable_receiver ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Additionally, verify that mutable borrows work correctly by testing actual mutation:
fn main() {
// Test with mutable self receiver
let mut counter = Counter { count: 0 };
*counter.get_mut_ref() = 42;
assert_eq!(counter.count, 42);
// Test with RefCell
let state = State { value: RefCell::new(100) };
*state.modify() = 200;
assert_eq!(*state.value.borrow(), 200);
println!("All borrow patterns verified successfully!");
}
5. Common Pitfalls
Pitfall 1: Forgetting that trait methods have specific receiver requirements
When implementing traits, the method signature is often fixed. You cannot change &self to &mut self if the trait defines &self.
trait Serializable {
fn serialize(&self) -> Vec<u8>; // Cannot return &mut self
}
Fix: Implement a separate mutable method outside the trait, or redesign the API.
Pitfall 2: Confusing &self with self
New Rust developers sometimes think that because a method takes &self, they can return a mutable reference by copying/cloning. This only works for Copy types.
// This will NOT work for non-Copy types
impl Data {
fn clone_data(&self) -> &Data {
// Error: returning reference to local data
// The temporary clone would be dropped immediately
&Data { inner: self.inner.clone() }
}
}
Pitfall 3: Attempting to pattern match around the borrow checker
impl Wrapper {
fn try_get_mut(&self) -> Option<&mut Inner> {
match self {
Wrapper { inner } => Some(inner), // Still borrows `self` immutably
}
}
}
You cannot use pattern matching to “extract” a mutable reference from an immutable borrow.
Pitfall 4: Nested borrows in closures
impl Cache {
fn get_or_insert(&self, key: Key) -> &mut Value {
if !self.map.contains_key(&key) {
let value = Value::default();
self.map.insert(key, value); // Cannot mutably borrow while &self is active
}
self.map.get_mut(&key).unwrap()
}
}
Pitfall 5: Inconsistent mutability across methods
struct Container {
items: Vec<i32>,
}
impl Container {
fn first(&self) -> &i32 {
&self.items[0]
}
fn update_first(&self) -> &mut i32 {
&mut self.items[0] // E0384 - inconsistent with first()
}
}
Both methods take &self, but one returns immutable and the other tries to return mutable. Make the mutable one take &mut self instead.
6. Related Errors
E0505: Cannot move out of self because it is borrowed
This error occurs when you try to move a value out of a struct while a reference to part of it still exists.
impl Foo {
fn consume(self) -> Vec<i32> {
self.data // E0505: cannot move out of borrowed content
}
}
E0506: Cannot assign to self because it is borrowed
When you have an immutable borrow of a field, you cannot assign to that same field.
struct Inner(u8);
impl Wrapper {
fn update(&self, val: u8) {
let _ref = &self.inner; // Immutable borrow
self.inner = Inner(val); // E0506: cannot assign to `self.inner` because it is borrowed
}
}
E0596: Cannot borrow self as mutable because it does not live long enough
Typically occurs when returning references that don’t outlive the borrow.
impl Parser {
fn parse(&mut self) -> &[u8] {
let result = &self.buffer[..];
self.clear(); // Error if trying to clear and return
result
}
}
E0502: Cannot borrow self as mutable because it is also borrowed as immutable (same borrow region)
A stricter version of E0384 when the compiler detects the exact same borrow causing the conflict.
impl Widget {
fn update(&mut self) {
self.render(); // If render takes &self, this conflicts
self.state = State::Updated;
}
}