Confused by traits, phantom lifetimes, where clauses, and Drop implementations


#1

but only in that precise combination. I don’t even know where to start describing what is happening here:

pub struct O<'a>(&'a ());
pub struct V<T>(T);

pub trait H<'a,T> {}

impl<'a> Default for O<'a> {
    fn default() -> O<'a> {
        O(&())
    }
}

impl<'a,'b,'c> H<'a,O<'b>> for V<O<'c>>
where
   'a: 'b,
{
}

impl<'a,T> V<T>
where
    T: Default,
{
    pub fn new() -> V<T> {
        V(T::default())
    }

    pub fn o<'b>(&'b mut self) -> O<'b>
    where
        V<T>: H<'b,T>,
    {
        O(&())
    }
}

impl<'a,T> Drop for V<T>
{
    fn drop(&mut self) {
    }
}

fn main()
{
    let mut tmpv: V<O> = V::new();
    let _o: O = tmpv.o();
}

This is a heavily simplified version of real-life application code I’m working on. The error message I get is

error[E0597]: `tmpv` does not live long enough
   |
43 |     let _o: O = tmpv.o();
   |                 ^^^^ borrowed value does not live long enough
44 | }
   | - `tmpv` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

Removing O's unused lifetime fixes things, as does removing the (pointless) where clause, the empty trait, or the (empty) Drop implementation. All of these seem unrelated to the error message. Help?


#2

You’re getting this error because the lifetime requirements you’ve created are impossible to satisfy. This is much easier to see if you name the lifetime in V<T>::o as 'a rather than 'b.

Then you have

    pub fn o<'a>(&'a mut self) -> O<'a>
    where
        V<T>: H<'a,T>

and

impl<'a,'b,'c> H<'a,O<'b>> for V<O<'c>>
where
   'a: 'b,
{
}

And finally rewrite let mut tmpv: V<O> = V::new(); as

    let o = O::default();
    let mut tmpv: V<O> = V(o);

When you call tmpv.o(), the lifetime 'a is the lifetime of tmpv. Now that we’ve renamed the lifetimes, the lifetime 'a in H is the same. In order to satisfy V<T>: H<'a, T>, T must be O, which it is. But the lifetime bound 'a: 'b means that tmpv must live at least as long as o. Because o precedes tmpv on the stack, it obviously cannot live as long.

rustc’s error reporting is getting a bit confused here but that’s what’s going on. To convince yourself of this, note that if you remove the return value of o you’ll still see the error message, but if you flip the lifetime bound on the H trait so that 'b: 'a your code will compile.


#3

Sorry, I appear to have “simplified” the example until it became erroneous, in precisely the way that you explained. Thanks for that; my original problem remains, but I guess it only appears when PhantomData is used. I’ll have another go at simplifying the code.


#4

It’s unlikely to be PhantomData that’s causing issues, but rather the Drop impl - a Drop impl requires a strict outlives relationship between your type and any references it contains. For instance, your original code compiles as-is if you remove the Drop impl. If you add Drop impl, you may have to explain the lifetime bounds a bit more explicitly, such as what @khuey showed.

If you can provide a more accurate repro, it would help to work through it.


#5

I found that very confusing, I would have expected an empty Drop impl not to make a difference. Your explanation makes sense, though.

I currently think my problem was that I was defiining a type with an associated lifetime and expecting that lifetime to be strictly contained in the type’s lifetime: something like

use std::marker::PhantomData;
pub struct R<'a>(pub PhantomData<&'a ()>);
pub struct S<'a>(&'a R<'a>);

impl<'a> S<'a> {
    pub fn x<'b>(&'b mut self) -> &'a R<'a>
    where
        'b: 'a
    {
        self.0
    }
}

impl<'a> Drop for R<'a> {
    fn drop(&mut self) {}
}

impl<'a> Drop for S<'a> {
    fn drop(&mut self) {}
}

fn main() {
    let r: R = R(PhantomData::default());
    let mut s: S = S(&r);
    let _x = s.x();
}

That doesn’t compile because R cannot be associated with S's lifetime, I think. even though that lifetime is never used. Again, that makes perfect sense as a rule, but it was a rule I was unaware of.


#6

Yeah, the body of drop() doesn’t matter.

In the nightly compiler, you can use the dropck_eyepatch feature to allow your code to compile as-is:

#![feature(dropck_eyepatch)]
use std::marker::PhantomData;
pub struct R<'a>(pub PhantomData<&'a ()>);
pub struct S<'a>(&'a R<'a>);

impl<'a> S<'a> {
    pub fn x<'b>(&'b mut self) -> &'a R<'a>
    where
        'b: 'a,
    {
        self.0
    }
}

impl<'a> Drop for R<'a> {
    fn drop(&mut self) {}
}

// The change is in how we define the `Drop` impl here
unsafe impl<#[may_dangle]'a> Drop for S<'a> {
    fn drop(&mut self) {}
}

fn main() {
    let r: R = R(PhantomData::default());
    let mut s: S = S(&r);
    let _x = s.x();
}

But you probably don’t need to define x() the way you did. The following should work (all else the same):

pub fn x(&mut self) -> &R<'a>
{
    self.0
}