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