Lifetime Specifiers for Traits with Closures

I have traits and objects defined as follows:

trait Container<T> {
    // This is more nontrivial (but with the same type signature) in the real code
    fn extract(self) -> T;
}
struct DummyContainer<T> {
    value: T
}
impl<T> DummyContainer<T> {
    pub fn new(val: T) -> DummyContainer<T> {
        DummyContainer {value: val}
    }
}
impl<T> Container<T> for DummyContainer<T> {
    fn extract(self) -> T {
        self.value
    }
}

and

trait Evaluator {
    fn compute<T, F: Fn() -> T>(func: F) -> Box<dyn Container<T>>;
}

to do computations that produce T and place them into Containers.

I tried to implement the trait as follows:

impl Evaluator for DummyEvaluator {
    fn compute<T, F> (func: F) -> Box<dyn Container<T>> 
    where
        F: Fn() -> T
    {
        Box::new(DummyContainer::new(func()))
    }
}

However, I get the error error[E0310]: the parameter type T may not live long enough so that the type DummyContainer<T> will meet its required lifetime bounds when trying to compile this. I'm not sure what the issue is, however, since the return value of F is immediately passed into the DummyContainer, which takes ownership of the T and drops the T when it is itself dropped.

How can I add lifetime annotations to resolve this issue? (I suppose another workaround would be cloning the result of func() and adding a T: Clone type bound, but I'd prefer not to do this.)

Edit: Changing the last impl to

let func_val = func();
Box::new(DummyAwaitable::new(func_val.clone()))

doesn't solve the error.

The problem here is that Box<dyn Container<T>> is the same as Box<dyn Container<T> + 'static>.

Lifetime bounds S: 'a for a type S basically mean that all the lifetimes that S “contains” (syntactically) are longer than 'a. Something like String: 'a holds for any lifetime 'a, OTOH for a reference type, e.g. &'b str: 'a only is fulfilled if 'b: 'a, i.e. “''b outlives 'a”.

Now as I already indicated above, a trait object type such as dyn Container<T> always also has a lifetime; if it’s not written explicitly then it’s deduced by some “simple” rules, explicit would be to write dyn Container<T> + 'a for some lifetime 'a. In this case when used in a Box<…> the rules work out to make dyn Container<T> mean the same as dyn Container<T> + 'static.

In the general case, a type S can be turned into a trait object dyn Trait + 'a if both S: Trait and S: 'a are fulfilled.

Back to the example. You’re trying, in your fn compute implementation, to turn DummyContainer<T> into dyn Container<T> + 'static (by the way, let’s ignore the fact that this can only happen inside of a box). The requirement DummyContainer<T>: Container<T> is not a problem, but DummyContainer<T>: 'static is a problem. See, T is a type argument and thus it may syntactically contain any kinds of lifetimes, T might be some type &'b str where 'b is shorter than 'static. However, in such a case, the type DummyContainer<T> would be DummyContainer<&'b str>, which contains the lifetime 'b syntactically as well. This means DummyContainer<&'b str>: 'static is violated if 'b is shorter than 'static.

Now, one way to go about solving the problem is to just restrict T to never contain such lifetimes. This would be written as T: 'static, adding this bound to fn compute (both in the trait and the impl) will probably make your code compile. If that’s to restrictive, you could add a lifetime argument to the trait Evaluator. I.e. create Evaluator<'a> with fn compute<T: 'a, F> (func: F) -> Box<dyn Container<T> + 'a> (where F: Fn() -> T). The approach with a lifetime argument is – in a sense – a generalization of the 'static one, since Evaluator<'static> would then have exactly the 'static bounds that Evaluator has in the other approach.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.