Poor diagnostic: mutable diagnostic cannot be "immutablized"
A friend stumbled on this the other day and I couldn't reason about it for the life of me, even having spend over an year on the language. I asked on rust-beginners but since it's not logged I lost the response. I was told that this is not needed for correctness but it's unfixable due to interactions with Cell and friends.
If that's really the case we should try to spill a better error message.
Example code
struct CPU {
pc: usize,
memory: Vec<String>,
}
impl CPU {
fn fetch(&mut self) -> &str {
self.pc += 1;
&self.memory[self.pc]
}
fn execute(&self, _: &str) {
}
fn test(&mut self) {
let instruction = self.fetch();
self.execute(instruction);
}
}
fn main() {}
Compiler output
rustc 1.14.0 (e8a012324 2016-12-16)
error[E0502]: cannot borrow `*self` as immutable because it is also borrowed as mutable
--> <anon>:19:9
|
18 | let instruction = self.fetch();
| ---- mutable borrow occurs here
19 | self.execute(instruction);
| ^^^^ immutable borrow occurs here
20 | }
| - mutable borrow ends here
error: aborting due to previous error
I also happened to encounter this today. I have trimmed down my code as follows and the same problem still occurs.
fn test() { let mut v = 0u8; let r = func(&mut v); println!("{}", v); } fn func(v: &mut u8) -> &u8 { v }By inlining func(), I get this:
fn test() { let mut v = 0u8; let r: &u8; { r = &mut v; } println!("{}", v); }ris immutable, so I thought that this program is valid, but the compiler still says:error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable --> borrow.rs:5:20 | 4 | { r = &mut v; } | - mutable borrow occurs here 5 | println!("{}", v); | ^ immutable borrow occurs here 6 | } | - mutable borrow ends here error: aborting due to previous errorKAMADA Ken'ichi at 2017-01-26 14:10:25
@arthurprs Your
fetchfunction takes a mutable reference toselfand returns a reference to astr. The lifetime of the returned&stris bound to the lifetime of&mut self, so as long as the reference is in scope, the variableselfis mutably borrowed and cannot be used otherwise which is what rustc is saying.Your code would compile (and run correctly) if we could "downgrade" the mutable borrow of
selfto an immutable borrow after execution thefetchfunction.But to show you why rust cannot just "downgrade" a mutable borrow to an immutable one in general, consider this quite similar code which makes use of interior mutability with
Cell:use std::cell::Cell; struct X { inner: Cell<u8>, } impl X { fn func1(&mut self) -> &u8 { // We return the &mut u8 from get_mut as &u8. self.inner.get_mut() } fn func2(&self, v: &u8) { // When this function is called from main, // v points to the u8 in the cell self.inner. println!("Before: {}", v); // The cell type allows us to set its inner data // using an immutable reference to the cell. self.inner.set(6); // Oops. println!("After: {}", v); } } fn main() { let mut x = X { inner: Cell::new(5) }; let v: &u8 = x.func1(); // Here, we borrow x mutably... x.func2(v); // ... and here immutably. }If this code would compile (it does not), it would print:
Before: 5 After: 6So, in
func2the value to whichvpoints changes during the execution of the function even thoughvis an immutable reference!In summary, I'd say that the error message provided by rustc is actually correct and clear.
Felix Wiedemann at 2017-02-08 17:26:43
I'm going to leave this open, actually. I think we can detect the
&mut -> &borrow case and then print ahelp: Reference mutability cannot be downgraded to immutableor something like that (though this wording is probably terrible). At the very least, we should mention this in the extended explanation for E0502.Mark Rousskov at 2017-05-22 16:26:49
The current output is slightly clearer:
error[E0502]: cannot borrow `*self` as immutable because it is also borrowed as mutable --> src/main.rs:17:9 | 16 | let instruction = self.fetch(); | ---- mutable borrow occurs here 17 | self.execute(instruction); | ^^^^ ----------- mutable borrow later used here | | | immutable borrow occurs hereAll the code in the first comment now compiles.
The explanation for why the original code should be rejected is now:
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable --> src/main.rs:30:5 | 29 | let v: &u8 = x.func1(); // Here, we borrow x mutably... | - mutable borrow occurs here 30 | x.func2(v); // ... and here immutably. | ^ - mutable borrow later used here | | | immutable borrow occurs hereEsteban Kuber at 2019-04-27 15:42:45
Hm, yeah, I agree that this is much better, but I think changing the "mutable borrow later used here" text to something like "mutable borrow's lifetime later used here" would be much clearer, as
vis clearly not a mutable borrow.Mark Rousskov at 2019-09-10 01:38:19
Triage: no change
Esteban Kuber at 2023-03-18 02:53:14