1

Consider the following struct:

struct State<'a> {
    parent: Option<&'a mut State<'a>>,
    // ...
}

My state stores some values that I might need later. Now I want to implement substates, i.e. allow the manipulation of those values in the substate without touching the parent state, but forwarding lookup of values not in the substate to its parent. Unfortunately, I need a mutable reference to every parent state at all times. I tried the following, but it doesn't work (Playground):

impl<'a> State<'a> {
    fn substate<'b>(&'b mut self) -> State<'b>
    where
        'a: 'b,
    {
        State::<'b> { parent: Some(self) }
    }
}

This gives the following error message:

error[E0308]: mismatched types
  --> src/main.rs:10:36
   |
10 |         State::<'b> { parent: Some(self) }
   |                                    ^^^^ lifetime mismatch
   |
   = note: expected mutable reference `&mut State<'b>`
              found mutable reference `&mut State<'a>`
note: the lifetime `'b` as defined here...
  --> src/main.rs:6:17
   |
6  |     fn substate<'b>(&'b mut self) -> State<'b>
   |                 ^^
note: ...does not necessarily outlive the lifetime `'a` as defined here
  --> src/main.rs:5:6
   |
5  | impl<'a> State<'a> {
   |      ^^

I don't understand why the compiler wants 'b to outlive 'a. In fact, the parent of a state will always live longer than its substate, so in my case the opposite is always true. So why can't the compiler just downgrade the "longer" lifetime 'a into the "shorter" lifetime 'b?

msrd0
  • 7,816
  • 9
  • 47
  • 82
  • Related: [Why does this mutable borrow live beyond its scope?](https://stackoverflow.com/q/66252831/2189130) – kmdreko Jun 28 '22 at 17:28

2 Answers2

3

The function signature is unsound, and here's an example of why:

struct State<'a> {
    parent: Option<&'a mut State<'a>>,
}

impl<'a> State<'a> {
    fn substate<'b>(&'b mut self) -> State<'b>
    where
        'a: 'b,
    {
        /* implementation hidden but assumed to be implemented as described */
    }
}
// [root]
let mut root = State { parent: None };

// [foo] -> [root]
let mut foo = root.substate();

{
    // [bar] -> [foo] -> [root]
    let mut bar = foo.substate();

    // [bar] -> [foo] -> [tmp]
    let mut tmp = State { parent: None };
    bar.parent.as_mut().unwrap().parent = Some(&mut tmp);

    // tmp is dropped
}

// [foo] -> {uninitialized memory}
drop(foo);

(Full playground example)

The function signature allows us to store a reference inside foo here that lives shorter than foo itself does, so after we leave the block foo points to uninitialized memory, which is undefined behavior and bad.

If you run the above code with cargo miri, it will give this error, confirming that it is indeed undefined behavior:

error: Undefined Behavior: type validation failed at .parent.<enum-variant(Some)>.0: encountered a dangling reference (use-after-free)
  --> src/main.rs:40:10
   |
40 |     drop(foo);
   |          ^^^ type validation failed at .parent.<enum-variant(Some)>.0: encountered a dangling reference (use-after-free)
   |
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

   = note: inside `main` at src/main.rs:40:10

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to previous error
Frxstrem
  • 38,761
  • 9
  • 79
  • 119
2

The error message on nightly is better (thanks to NLL stabilization that may be removed before hitting stable, but now it is there):

error: lifetime may not live long enough
  --> src/main.rs:10:31
   |
5  | impl<'a> State<'a> {
   |      -- lifetime `'a` defined here
6  |     fn substate<'b>(&'b mut self) -> State<'b>
   |                 -- lifetime `'b` defined here
...
10 |         State::<'b> { parent: Some(self) }
   |                               ^^^^^^^^^^ this usage requires that `'b` must outlive `'a`
   |
   = help: consider adding the following bound: `'b: 'a`
   = note: requirement occurs because of a mutable reference to `State<'_>`
   = note: mutable references are invariant over their type parameter
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

It is not enough that 'a outlives 'b, it should be exactly the same since it is invariant and invariant lifetimes cannot be shortened. Look at the link the compiler gives or the reference entry about variance to better understand what variance is and why mutable references are invariant.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • Agreed, that error message is indeed a lot better. Is there any way to allow subtyping here? As far as I can tell, it is ok in my case to downgrade the lifetime, just the compiler doesn't know that. – msrd0 Jun 28 '22 at 15:12
  • @msrd0 There is no way to opt-out variance. However, using the same lifetime twice with mutable references is almost always wrong. If I'd had to guess, I would guess you are trying to create a self-referential struct and arrived at this annotation because of compiler errors. Am I correct? – Chayim Friedman Jun 28 '22 at 15:19
  • Nope, nothing in there is self-referential. I just want to create substates of substates of substates ... except I need to track when I access part of the state, hence the mutable reference – msrd0 Jun 28 '22 at 15:20
  • @msrd0 I can't help you without more code (which will probably be appropriate for a new question), but I'm not really convinced ignoring variance here is sound. I think the "meowing dogs" problem still exists here. – Chayim Friedman Jun 28 '22 at 15:23
  • I ended up using `RefCell` which, unlike mutable references, does not seem to prevent subtyping the lifetime parameter. – msrd0 Jun 28 '22 at 16:00
  • 1
    @msrd0 `RefCell` has interior mutability and therefore *must* be invariant over its generic parameter, so I'm curious what change you made if `RefCell` fixes it. – kmdreko Jun 28 '22 at 20:24