rust icon indicating copy to clipboard operation
rust copied to clipboard

Lifetime mismatch from itself

Open xplorld opened this issue 5 years ago • 5 comments

I found myself encountering error[E0623], but the error message is not understandable and seems buggy: it says some variable's lifetime mismatches from itself.

Minimum Reproducible:

#[derive(Debug)]
enum IntOrRef<'a> {
    I(u32),
    R(&'a u32),
}

#[derive(Debug)]
struct V<'a> {
    v: Vec<IntOrRef<'a>>,
}

fn f(v: &mut V) {
    match v.v.last().unwrap() {
        IntOrRef::I(i) => {v.v.push(IntOrRef::R(&i));}
        IntOrRef::R(_)=> {}
    }
}

fn main() {
    let mut v = V { v: vec![IntOrRef::I(1)] };
    f(&mut v);
    dbg!(v);
}

Result:

error[E0623]: lifetime mismatch
  --> src/bin/temp.rs:14:37
   |
12 | fn f(v: &mut V) {
   |         ------
   |         |
   |         these two types are declared with different lifetimes...
13 |     match v.v.last().unwrap() {
14 |         IntOrRef::I(i) => {v.v.push(IntOrRef::R(&i));}
   |                                     ^^^^^^^^^^^^^^^ ...but data from `v` flows into `v` here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0623`.

For me, this is not understandable, and I do not know how to fix it. In the real code, I want to copy a long-lived variable's short-lived reference to a new one and failed. I wonder:

  1. How should I annotate lifetimes to get this work?
  2. What does it mean by "mismatching from itself"?

Meta

rustc --version --verbose:

rustc 1.35.0 (3c235d560 2019-05-20)
binary: rustc
commit-hash: 3c235d5600393dfe6c36eeed34042efad8d4f26e
commit-date: 2019-05-20
host: x86_64-apple-darwin
release: 1.35.0
LLVM version: 8.0

xplorld avatar Jun 11 '19 02:06 xplorld

Im not a 100% sure Im right, so if anyone notices a mistake, please point it out :)

  1. I dont think you can make this work... .last() gives you a reference to the last item of the vec. You try and push onto the vec while this reference is alive. What happens if that push had to reallocate the vector? Now you have a reference to memory that isn't yours anymore, Undefined Behaviour.

You could argue that a u32 implements Copy, so you could just #[derive(Copy, Clone)] your enum, and copy out of the reference you get from .last(). Except what happens next is that you would push a reference to the copy (Being on the stack) onto the vec...and after your function returns, the copy is popped of the stack, and the vector would contain a reference to invalid memory...Undefined Behaviour.

Rust disallows both of these forms of UB, and sometimes it spits out weird error messages.

DutchGhost avatar Jun 11 '19 08:06 DutchGhost

With nll enabled, the output is:

error: lifetime may not live long enough
  --> src/main.rs:14:11
   |
13 | fn f(v: &mut V) {
   |      -  - let's call the lifetime of this reference `'1`
   |      |
   |      has type `&mut V<'2>`
14 |     match v.v.last().unwrap() {
   |           ^^^^^^^^^^ argument requires that `'1` must outlive `'2`

error[E0502]: cannot borrow `v.v` as mutable because it is also borrowed as immutable
  --> src/main.rs:15:28
   |
13 | fn f(v: &mut V) {
   |      - has type `&mut V<'1>`
14 |     match v.v.last().unwrap() {
   |           ----------
   |           |
   |           immutable borrow occurs here
   |           argument requires that `v.v` is borrowed for `'1`
15 |         IntOrRef::I(i) => {v.v.push(IntOrRef::R(&i));}
   |                            ^^^ mutable borrow occurs here

estebank avatar Sep 20 '19 22:09 estebank

A simplified example that gives a similar error:

struct Foo;
fn mut_ref<'a, 'b>(val: &'a mut &'b mut Foo)  {
    let tmp: &'b mut Foo = *val;
}

Gives the following error:

error[E0623]: lifetime mismatch
 --> src/main.rs:9:28
  |
8 | fn mut_ref<'a, 'b>(val: &'a mut &'b mut Foo)  {
  |                         -------------------
  |                         |
  |                         these two types are declared with different lifetimes...
9 |     let tmp: &'b mut Foo = *val;
  |                            ^^^^ ...but data from `val` flows into `val` here

error: aborting due to previous error

It's not clear to me what it means for data from val to flow into itself.

Aaron1011 avatar Mar 05 '20 23:03 Aaron1011

@Aaron1011 with nll enabled:

error: lifetime may not live long enough
 --> src/lib.rs:4:14
  |
3 | fn mut_ref<'a, 'b>(val: &'a mut &'b mut Foo)  {
  |            --  -- lifetime `'b` defined here
  |            |
  |            lifetime `'a` defined here
4 |     let tmp: &'b mut Foo = *val;
  |              ^^^^^^^^^^^ type annotation requires that `'a` must outlive `'b`
  |
  = help: consider adding the following bound: `'a: 'b`

estebank avatar Mar 06 '20 23:03 estebank

What is the soundness argument for this error?

A reason why I believe this is sound:

// For every function foo like
fn foo<'long>(r: &'long mut T) -> &'long mut T { ... }

// It should be possible to write a function bar like
fn bar<'short, 'long: 'short>(p: &'short mut &'long mut T) {
    *p = foo(*p);  // doesn't compile
}

// Since inlining bar is accepted.
fn main() {
    let mut x = Default::default();
    let mut r = &mut x;
    r = foo(r);  // compiles
    bar(&mut r);  // should be possible
}

So this looks like a type system limitation rather than a soundness error.

Note that this issue seems similar to #62400, #59299, and #44409.

ia0 avatar Feb 07 '22 14:02 ia0