"parameter type may not live long enough”: difference between impl and dyn

Hi there,

apologies if this was already answered, I’ll be thankful for pointers in that case.

The issue I’m running into is that a locally owned value captured by a closure and then returned in a Box may or may not work, depending on how the Box is declared:

pub trait X: Clone + Send + Unpin + Sync {
    fn x(&self) -> Box<dyn Future<Output = ()> + Unpin + Send>;
}

pub fn c<X1: X>(x: &X1) -> Box<impl Future<Output = ()> + Unpin + Send> {
    let x = x.clone();
    let res = x.x().then(move |_| x.x());
    Box::new(res)
}

(here Future comes from the futures preview 0.3.0-alpha18 and Box is the standard one)

The above compiles successfully, as I think it should. Changing the return type definition of c by substituting dyn for impl makes it fail with the following error message:

the parameter type `X1` may not live long enough

note: ...so that the type `futures::future::Then<std::boxed::Box<dyn futures::Future<Output = ()> + std::marker::Send + std::marker::Unpin>, std::boxed::Box<dyn futures::Future<Output = ()> + std::marker::Send + std::marker::Unpin>, [closure@ipfs/src/lib.rs:102:26: 102:40 x:X1]>` will meet its required lifetime bounds

Removing the type parameter by using impl syntax does not change this, as does adding explicit lifetime annotations.

My reasoning for expecting this to compile is that the closure captures a fresh clone that should be able to live as long as is necessary; perhaps the error message is just pointing me in the wrong direction here.

Many thanks,

Roland

The problem is that X might contain references inside itself which do not satisfy 'static (Box<dyn Future ...> is shorthand for Box<dyn Future ... + 'static>). This isn't an issue for impl Future as it implicitly captures all generic parameters of the function, so if X cannot live for 'static the returned value will also have a shorter lifetime.

The fix is to explicitly specify X1: X + 'static (or put the constraint directly on the X trait if all implementations should always satisfy it); or allow the returned value to have a shorter lifetime by explicitly specifying it in the signature:

pub fn c<'a, X1: X + 'a>(x: &X1) -> Box<impl Future<Output = ()> + Unpin + Send + 'a>
1 Like

Ah, that was (of course) the only combination of lifetime annotations I had not tried (I had only placed them on the reference, not the type). This made it click for me: x is of an existential type that implements X, so since the function cannot pick a concrete type, all possible types must have an appropriate lifetime to fit into the box.

Now the issue I still have is that the box doesn’t really contain x but a Clone of it, and my understanding is that cloning means I create a completely new object that does not contain any borrowed references. Is that wrong?

If X1 itself is a type that contains references then cloning it will return a new owned value that still contains references and is subject to the borrow checker. The simplest example is if someone directly implements X for a reference, e.g. this playground.

1 Like

So the solution is to demand that X1 live long enough, excluding that such “hidden” references ruin the day. Thanks for the explanation!

Still, the question then remains why this is no issue for the code I posted at the top: using impl seems to somehow ignore this issue. My guess is that the trade-offs in the addition of the impl syntax (see RFC1951) entail that such incorrect behavior may slip through — or will this be caught at the calling site, then?

Ah, further experimentation shows that e.g. demanding 'static lifetime of the impl Future leads to a compiler error, as it should. So all is good, I think.