Poor diagnostic: mutable diagnostic cannot be "immutablized"

e6b6d64
Opened by Arthur Silva at 2023-03-18 02:53:14

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
  1. 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);
    }
    

    r is 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 error
    

    KAMADA Ken'ichi at 2017-01-26 14:10:25

  2. @arthurprs Your fetch function takes a mutable reference to self and returns a reference to a str. The lifetime of the returned &str is bound to the lifetime of &mut self, so as long as the reference is in scope, the variable self is 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 self to an immutable borrow after execution the fetch function.

    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: 6
    

    So, in func2 the value to which v points changes during the execution of the function even though v is 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

  3. I'm going to leave this open, actually. I think we can detect the &mut -> & borrow case and then print a help: Reference mutability cannot be downgraded to immutable or 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

  4. 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 here
    

    All 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 here
    

    Esteban Kuber at 2019-04-27 15:42:45

  5. 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 v is clearly not a mutable borrow.

    Mark Rousskov at 2019-09-10 01:38:19

  6. Triage: no change

    Esteban Kuber at 2023-03-18 02:53:14