Why does RefCell influence lifetimes?

I can't figure out why the presence of the RefCell in the following code influences the lifetimes.

use std::cell::RefCell;

pub struct Foo<'f> {
    pub value: RefCell<&'f usize>,
}

pub struct Wrapper<'w> {
    pub foo: &'w Foo<'w>,
    pub bar: &'w usize,
}

fn main() {
    let foo = Foo {
        value: RefCell::new(&0),
    };

    let bar = 0;

    {
        let _ = Wrapper {
            foo: &foo,
            bar: &bar,
        };
    }
} // line 25

When RefCell is used in Foo, the compiler exits with the following error.

error[E0597]: `bar` does not live long enough
  --> src\main.rs:22:19
   |
22 |             bar: &bar,
   |                   ^^^ borrowed value does not live long enough
...
25 | }
   | - `bar` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

However, when I remove the RefCell, bar lives long enough. Another way to get this code to compile is changing the order of the declarations of foo and bar.

So, I have two questions.

  1. Why does RefCell influence the lifetimes?
  2. Why does the order of foo and bar has an influence?

I think this is due to lifetime variance. The docs for this topic are here.

The main bit is that, normally, Rust can shorten lifetime of a reference. For example, if you have a &'static i32, you can use it instead of any shorter &'a i32. However in some cases, including RefCell, such shortening is prohibited. This is most reasonable, suppose you could pass a RefCell<&'static i32> into a function which wants RefCell<&'a i32>, like this:

fn spooky<'a>(cell: &RefCell<&'a i32>, x: &'a i32) { 
    *cell.borrow_mut() = x 
}

Then calling such function would result in a &'a i32 stored inside RefCell<&'static i32>, and that's wrong, and will become a dangling reference as soon as 'a ends.

So, the Wrapper enforces that lifetimes of &i32 and the once inside RefCell are equal, but, to make them equal, compiler is only allowed to shorten the first one. So this only works if bar outlives the foo.

3 Likes