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:
- The type
bar
is Contravariant<&'x i32>
.
- The
error
function accepts any subtype of Contravariant<&'a i32>
, where 'a
is the lifetime of that &mut s
expression.
- Thus
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>
.
- So the subtyping relation can be satisfied when
&'x i32
is a supertype of &'a i32
.
- Thus
'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 here1 --+---+
// |
// <--- 'a ends here2 --+
}
// 1 when `bar` goes out of scope
// 2 '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 -------+