fn works<'a>(foo: &Option<&'a mut String>, s: &'a mut String) {}
fn error<'a>(foo: &RefCell<Option<&'a mut String>>, s: &'a mut String) {}
let mut s = "hi".to_string();
let foo = None;
works(&foo, &mut s);
// with this, it errors
// let bar = RefCell::new(None);
// error(&bar, &mut s);
s.len();
If I put in the two lines with the comment, the following error occurs:
error[E0502]: cannot borrow `s` as immutable because it is also borrowed as mutable
--> <anon>:16:5
|
14 | error(&bar, &mut s);
| - mutable borrow occurs here
15 |
16 | s.len();
| ^ immutable borrow occurs here
17 | }
| - mutable borrow ends here
The signatures of works()
and errors()
look fairly similar. But apparently the compiler knows that you can cheat on it with a RefCell
, because the borrow checker behaves differently.
I can even "hide" the RefCell
in another type of my own, but the compiler still always does the right thing (errors in case a RefCell
could be used). How does the compiler know all that stuff and how does it work? Does the compiler mark types as "interior mutability container" or something like that?
RefCell<T>
contains an UnsafeCell<T>
which is a special lang item. It is UnsafeCell
that causes the error. You could check with:
fn error<'a>(foo: &UnsafeCell<Option<&'a mut String>>, s: &'a mut String) {}
...
let bar = UnsafeCell::new(None);
error(&bar, &mut s);
But the error is not due to compiler recognizing an UnsafeCell introduces interior mutability, but that an UnsafeCell
is invariant in T. In fact, we could reproduce the error using PhantomData:
struct Contravariant<T>(PhantomData<fn(T)>);
fn error<'a>(foo: Contravariant<&'a i32>, s: &'a mut String) {}
...
let bar = Contravariant(PhantomData);
error(bar, &mut s);
or even just anything that is contravariant or invariant in the lifetime 'a
:
fn error<'a>(foo: Option<fn(&'a i32)>, s: &'a mut String) {}
let bar = None;
error(bar, &mut s);
The reason you can't hide a RefCell is because variance is derived through the fields of the structure. Once you used RefCell<T>
somewhere, no matter how deep, the compiler will figure out T
is invariant.
Now let's see how the compiler determine the E0502 error. First, it's important to remember that the compiler has to choose two specific lifetimes here: the lifetime in the type of the expression &mut s
('a
) and the lifetime in the type of bar
(let's call it 'x
). Both are restricted: the former lifetime 'a
has to be shorter than the scope of s
, otherwise we would end up with a reference living longer than the original string. 'x
has to be larger than the scope of bar
, otherwise we could access an dangling pointer through bar
(if a type has a lifetime parameter the compiler assume the type can access a value with that lifetime).
With these two basic restriction, the compiler goes through the following steps:
bar
is Contravariant<&'x i32>
.error
function accepts any subtype of Contravariant<&'a i32>
, where 'a
is the lifetime of that &mut s
expression.bar
should be a subtype of Contravariant<&'a i32>
Contravariant<T>
is contravariant over T
, i.e. if U <: T
, then Contravariant<T> <: Contravariant<U>
. &'x i32
is a supertype of &'a i32
.'x
should be shorter than 'a
, i.e. 'a
should outlive 'x
. Similarly, for an invariant type, the derived relation is 'a == 'x
, and for convariant, 'x
outlives 'a
.
Now, the problem here is that the lifetime in the type of bar
lives until the end of scope (as per restriction mentioned above):
let bar = Contravariant(PhantomData); // <--- 'x starts here -----+
error(bar, // |
&mut s); // <- 'a starts here ---+ |
s.len(); // | |
// <--- 'x ends here¹ --+---+
// |
// <--- 'a ends here² --+
}
// ¹ when `bar` goes out of scope
// ² 'a has to outlive 'x
In both contravariant and invariant cases, 'a
outlives (or equals to) 'x
means the statement s.len()
must be included in the range, causing borrowck error.
Only in the covariant case we could make the range of 'a
shorter than 'x
, allowing the temporary object &mut s
be dropped before s.len()
is called (meaning: at s.len()
, s
is not considered borrowed anymore):
let bar = Covariant(PhantomData); // <--- 'x starts here -----+
// |
error(bar, // |
&mut s); // <- 'a starts here --+ |
// | |
// <- 'a ends here ----+ |
s.len(); // |
} // <--- 'x ends here -------+
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With