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
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.
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)
}
}
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.
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.
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?
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...)