Lifetime problems with reference in UnsafeCell

I originally opened this question on stackoverflow, it was marked as duplicate, but I don't think the linked questions or Shepmaster resolved my problem.

Consider the following code:

struct Test<'a> {
    d: PhantomData<UnsafeCell<&'a u8>>
}

impl<'a> Test<'a> {
    pub fn new() -> Self {
        Test { d: PhantomData }
    }
    pub fn test(&'a self){}
}

fn main() {
    let t = Test::new();
    (move || {
        t.test();
    })();
}

This fails with following error:

error[E0521]: borrowed data escapes outside of closure
  --> src/main.rs:18:9
   |
16 |     let t = Test::new();
   |         - `t` declared here, outside of the closure body
17 |     (move || {
18 |         t.test();
   |         ^^^^^^^^

Ok, I think the compiler is right, since the lifetime specifier indicates 'a lives outside of the closure, which mismatches with t that was moved into the closure. However, if I change the UnsafeCell to a direct reference, the error no longer appears. Same problem exists for any struct containing UnsafeCell, such as Cell, or Mutex (in the original question).

So my question is:

  • Which of the above code (with and without UnsafeCell) is valid? And why?
  • Why UnsafeCell would cause the error? Is there anything special about having a reference in it?

The difference is variance.

&'a T is covariant in the lifetime 'a: You can coerce an immutable reference with a longer lifetime to one with a strictly shorter lifetime, because it is always safe to pass &'long T where &'short T is expected. This is why the code compiles without the UnsafeCell.

But UnsafeCell<&'a T> is invariant in 'a because it has interior mutability: If you could pass UnsafeCell<&'long T> to code that takes UnsafeCell<&'short T>, that code could write a short-lived reference into your long-lived cell. So it is not safe to coerce an UnsafeCell to have a different lifetime.

(The same is true for any type that lets you mutate the reference it contains, e.g. Mutex<&'a T> or &mut &'a T.)

@shepmaster, none of the linked answers for the Stack Overflow question mention variance, so I don't think they fully answer the question.

7 Likes

Incidentally, if you free up the bound on &self in the test declaration, it also compiles.

    pub fn test(&self){}

(Unsure as to your actual situation versus the mock-up.)

Thanks for your help guys :slight_smile: , now I understand.

Feel free to post your answer there.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.