1

I have a problem with lifetimes of Cell (UnsafeCell/RefCell/...) references. From my understanding this code should compile:

fn test1<'a, 'b: 'a>(x: Cell<&'b u32>) {
    let x2: Cell<&'a u32> = x;
}

But it produces an error:

error[E0308]: mismatched types
 --> src/main.rs:4:29
  |
4 |     let x2: Cell<&'a u32> = x;
  |                             ^ lifetime mismatch
  |
  = note: expected type `std::cell::Cell<&'a u32>`
             found type `std::cell::Cell<&'b u32>`
note: the lifetime 'a as defined on the function body at 3:1...
 --> src/main.rs:3:1
  |
3 | / fn test1<'a, 'b: 'a>(x: Cell<&'b u32>) {
4 | |     let x2: Cell<&'a u32> = x;
5 | | }
  | |_^
note: ...does not necessarily outlive the lifetime 'b as defined on the function body at 3:1
 --> src/main.rs:3:1
  |
3 | / fn test1<'a, 'b: 'a>(x: Cell<&'b u32>) {
4 | |     let x2: Cell<&'a u32> = x;
5 | | }
  | |_^

I think : in <> is not well-known operator but I found it in some RFC when trying to resolve my problem.

I should be able to make a Cell with shorter lifetime out of one with longer. When I replace Cell type with some dummy wrapper everything works fine, so from my experiments it seems Cell (UnsafeCell etc) is treated specially when dealing with reference lifetimes.

This is not my original problem. I wanted to have some shared state between multiple structures - one main struct with RefCell and child structs with reference to RefCell but I cant get borrow checker happy without borrowing self for lifetime of entire object. See:

struct A<'a> {
    x: RefCell<&'a u32>,
}

impl<'a> A<'a> {
    fn new(x: &'a u32) -> A<'a> {
        A { x: RefCell::new(x) }
    }

    fn foo(&self) -> B<'a> {
        B { x: &self.x }
    }
}

struct B<'a> {
    x: &'a RefCell<&'a u32>,
}

If I add lifetime 'a to self in foo, it compiles but then fails for this code:

let fs = A::new(&x);
{
    fs.foo();
}
let fs2 = fs;

Error: error[E0505]: cannot move out of fs because it is borrowed

Is some other method to implement shared state between objects? I'm using a single thread so theres no synchronization problem for now.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Rafalh
  • 41
  • 4
  • 1
    See https://doc.rust-lang.org/nomicon/subtyping.html for an explanation of why it would be unsafe to let `Cell` be variant over the stored type. – interjay Sep 26 '17 at 22:26
  • 1
    To summarize: `Cell` is invariant over `T` because `Cell` can be mutated when stored in a `&`-ref, and allowing variance would mean you could store a reference with a shorter lifetime in the `Cell` (`let x2: &Cell<&'a u32> = &x; x2.set(somethingWithLifetimeA); x.get() // returns &'b T that really only lives for 'a`) – Lily Ballard Sep 26 '17 at 22:53
  • Thank you for pointing me to materials and providing examples of how variance works! This helped me to understand mr. borrow checker and come up with resolution for my problem. Didnt expect to find resolution so quickly :) – Rafalh Sep 27 '17 at 00:15

1 Answers1

3

As explained in the comments, my problem was caused by Cell type invariance. I managed to solve my original problem by using two lifetimes instead of using the same lifetime everywhere. Now &self is borrowed in foo for a shorter lifetime than 'a:

struct A<'a> {
    x: RefCell<&'a u32>,
}

impl <'a> A<'a> {
    fn new(x: &'a u32) -> A<'a> {
        A {
            x: RefCell::new(x),
        }
    }

    fn foo<'b>(&'b self) -> B<'b, 'a> {
        B {
            x: &self.x,
        }
    }
}

struct B<'b, 'a: 'b> {
    x: &'b RefCell<&'a u32>,
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Rafalh
  • 41
  • 4