Borrow checkers not recognizing distinct fields

#1

Consider the following example (cut down):

enum A {
    Var1(String),
    Var2(i32)
}
struct B;

struct Info {
    a: Option<Rc<RefCell<A>>>,
    b: Option<Weak<RefCell<B>>
}

fn foo(&mut info) -> Result<T, E> {
    // at some point
    if let Some(inner) = &info.a { // IMPORTANT LINE 2
        if let A::Var1(s) = &inner.borrow() as &A { // IMPORTANT LINE 3
            // some time later
            info.b = Some(...); // IMPORTANT LINE 1
        } else {
            Err(E)
        }
    } else {
        Err(E)
    }
}

@ Important Line 1: the borrow checker complains that @ Important Line 2 there is a conflicting immutable borrow. Why, if the these are different fields, does the borrow checker complain?

I did fine that adding a clone() before the borrow() @ Important Line 3 fixes the issue

#2

This code, with some minor syntax fixes, compiles without error: Playground.

The errors you are seeing must be caused by something that has been excluded from this cut-down example.

#3

I guess this was also important (let me try again):

enum A {
    Var1(String),
    Var2(i32)
}
struct B;

struct Info {
    a: Option<Rc<RefCell<A>>>,
    b: Option<Weak<RefCell<B>>
}

struct Contains {
    info: Rc<RefCell<info>>
}

fn foo(con: &Contains) -> Result<T, E> {
    // at some point
    
    let info = &mut con.info.borrow_mut();  // ADDED THIS LINE

    if let Some(inner) = &info.a { // IMPORTANT LINE 2
        if let A::Var1(s) = &inner.borrow() as &A { // IMPORTANT LINE 3
            // some time later
            info.b = Some(...); // IMPORTANT LINE 1
        } else {
            Err(E)
        }
    } else {
        Err(E)
    }
}
#4

Weird! If I move the if expression into a separate function, it compiles (Playground 1).

But the same code inlined into foo (Playground 2) generates this error:

error[E0502]: cannot borrow `*info` as mutable because it is also borrowed as immutable
  --> src/lib.rs:30:13
   |
27 |     if let Some(inner) = &info.a { // IMPORTANT LINE 2
   |                           ---- immutable borrow occurs here
28 |         if let A::Var1(s) = &inner.borrow() as &A { // IMPORTANT LINE 3
   |                              -------------- a temporary with access to the immutable borrow is created here ...
29 |             // some time later
30 |             info.b = Some(Weak::new()); // IMPORTANT LINE 1
   |             ^^^^ mutable borrow occurs here
...
35 |     } else {
   |     - ... and the immutable borrow might be used here, when that temporary is dropped and runs the destructor for type `std::cell::Ref<'_, A>`
   |
   = note: The temporary is part of an expression at the end of a block. Consider forcing this temporary to be dropped sooner, before the block's local variables are dropped. For example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block.

The compiler’s suggestion to store the result in a local variable does not fix the error.

The main difference is the type of info, which gets coerced from &mut RefMut<Info> to &mut Info in the non-inlined case. If I make the same change by explicitly dereferencing it in the inlined case:

let info = &mut *con.info.borrow_mut();

then the code again compiles without error (Playground 3). I’m not sure exactly what’s going on here, but maybe it’s a bug related to temporary lifetime extension.

#5

I guess the difference is that if info is a RefMut<Info> then info.b must implicitly call RefMut::deref_mut, which requires exclusive access to all of info.

But if info is an &mut Info then info.b can access the field “directly,” and then the compiler can treat it as separate from any other field borrow.

#6

I think that is it, thank you. I do think that the compiler should be able to provide a better error in this case.

edit: or maybe a clippy lint to encourage casting?

#7

Right, it fails because the implicit deref() borrows all of info just to access info.a, and then the implicit deref_mut() for info.b is blocked. But when you make it dereference the whole thing:

let info = &mut *con.info.borrow_mut();

Now info is a direct &mut Info, and the RefMut<Info> is an implicitly-saved temporary. The borrow checker is happy to let you access the distinct fields in this case.

(sorry for the edit mess – this is basically what you said, just slightly rephrased, so I’ll leave it…)