struct MyCell<T> {
value: T
}
impl<T> MyCell<T> {
fn new(value: T) -> Self {
MyCell { value }
}
fn get(&self) -> &T {
&self.value
}
fn set(&self, new_value: T) {
unsafe {
*(&self.value as *const T as *mut T) = new_value;
}
}
}
fn set_to_local(cell: &MyCell<&i32>) {
let local = 100;
cell.set(&local);
}
fn main() {
let cell = MyCell::new(&10);
set_to_local(&cell);
}
When calling cell.set(&local)
, suppose cell
is 'x
and '&local
is 'y
, I am told that the covariance rule will change the type of cell
from &MyCell<'x, &i32>
to &MyCell<'y, &i32>
.
How does the assignment inside the unsafe block affect the lifetime inference for the parameters of set()
? Raw pointers does not have lifetime then how does the compiler know it should make cell
and new_value
have the same lifetime using covariance?
How does the assignment inside the unsafe block affect the lifetime inference for the parameters of
set()
?
It doesn't — and this is not even specific to raw pointers or unsafe
. Function bodies never affect any aspect of the function signature (except for async fn
s which are irrelevant here).
Raw pointers does not have lifetime then how does the compiler know it should make
cell
andnew_value
have the same lifetime using covariance?
It sounds like you misread some advice. In the code you have, Cell<T>
is invariant in T
, but for an interior-mutable type to be sound, it must be invariant (not covariant) in the type parameter. In the code you have, the compiler infers covariance for T
because MyCell
contains a field that is simply of the type T
. Covariance is the “typical” case for most generic types.
Therefore, the code you have is unsound because MyCell
is covariant over T
but must instead be invariant over T
.
Your code is also unsound because in the implementation of set()
, you're creating an immutable reference to a T
, &self.value
, and then writing to its referent. This is “undefined behavior” no matter how you do it, because creating &self.value
asserts to the compiler/optimizer that the pointed-to memory won't be modified until the reference is dropped.
If you want to reimplement the standard library's Cell
, you must do it like the standard library does, with the UnsafeCell
primitive:
pub struct Cell<T: ?Sized> {
value: UnsafeCell<T>,
}
UnsafeCell
is how you opt out of &
's immutability guarantees: creating an &UnsafeCell<T>
doesn't assert that the T
won't be mutated. It is also invariant in T
, which automatically makes the containing type invariant in T
. Both of these are necessary here.