How to express the lifetime relationship in lambda capture

Consider this example

trait CurryLize2<T0,T1,U>{ 
    fn to_curry(self)->Box<dyn FnOnce(T0)->Box< dyn FnOnce(T1)->U>>;
}
impl<'a,'b,'c,F:'a,T0:'b,T1:'c,U> CurryLize2<T0,T1,U> for F 
where F: FnOnce(T0,T1)->U + 'static,
'b:'a,
'c:'a,
'b:'c
{
    fn to_curry(self)->Box< dyn FnOnce(T0)->Box<dyn FnOnce(T1)->U>> {
		Box::new(move |t0:T0|{
			Box::new(move |t1:T1|{
				self(t0,t1)
			})
		})
    }
}
fn show(v:i32,v2:u8)->f64{
	0.0
}
fn main(){
  	let f = show.to_curry();
	f(1)(2);
}

The compiler just rejects this example, and complains that:

the parameter type T0 may not live long enough ...so that the type T0 will meet its required lifetime bounds

I have used the lifetime parameters to express the relationship between their lifetimes. However, it does not work. In order to make the definition of to_curry work, how to express their lifetime?

This should work

trait CurryLize2<'a, 'b, T0, T1, U> {
    fn to_curry(self) -> Box<dyn FnOnce(T0) -> Box<dyn FnOnce(T1) -> U + 'b> + 'a>;
}
impl<'a, 'b, 'c, F: 'a, T0: 'b, T1: 'c, U> CurryLize2<'b, 'c, T0, T1, U> for F
where
    F: FnOnce(T0, T1) -> U + 'static,
    'b: 'a,
    'c: 'a,
    'b: 'c,
{
    fn to_curry(self) -> Box<dyn FnOnce(T0) -> Box<dyn FnOnce(T1) -> U + 'c> + 'b> {
        Box::new(move |t0: T0| Box::new(move |t1: T1| self(t0, t1)))
    }
}

Box<dyn Trait> is equal to Box<dyn Trait + 'static>. The error message is saying T0 may not live long enough (as static).

1 Like

playground

trait CurryLize2<'s, T0, T1, U>
where
    Self: 's,
{
    fn to_curry(self) -> Box<dyn FnOnce(T0) -> Box<dyn FnOnce(T1) -> U + 's> + 's>;
}

What does the lifetime parameter in the new syntax Box<dyn Trait + 's> mean?

What does 's in this syntax Box<dyn Trait + 's> mean?

So, the syntax Box<dyn Trait + 's> just requires that all types associated with the trait object should satisfy lifetime 's? In my example, we say T0: 's, hence T0 as the part of the closure type(due to the captured), which can still make the closure type satisfy 's?

Depends on what you mean with "types associated with the trait object" since it might be confused with associated types in traits.

I would say that dyn Trait + 's (it doesn't have to be inside a Box) says that whatever type is "behind" the dyn Trait, it is guaranteed to live for at least 's.

It's unclear to me what you're asking here...

I'm asking the reason why we just need to give the trait object a bound will make the example ok.

Right. T0 is the argument for Box< dyn FnOnce(T0)->Box<dyn FnOnce(T1)->U>>, and the captured type for Box<dyn FnOnce(T1)->U>.

A function or clousure that doesn't capture variables always satisfies the 'static bound (thus 's bound).
A closure cast to dyn FnOnce(...) -> ... + 's means if the closure captures a variable, the captured variable must live at least as long as 's.

1 Like

Let's simplify the problem:

trait Foo {}

fn make_dyn_foo<F: Foo>(f: F) -> Box<dyn Foo> {
    Box::new(f)
}

This code gives this error:

error[E0310]: the parameter type `F` may not live long enough
 --> src/lib.rs:4:5
  |
4 |     Box::new(f)
  |     ^^^^^^^^^^^ ...so that the type `F` will meet its required lifetime bounds
  |
help: consider adding an explicit lifetime bound...
  |
3 | fn make_dyn_foo<F: Foo + 'static>(f: F) -> Box<dyn Foo> {
  |                        +++++++++

The error is fundamentally the same as yours. Adding a 'static bound would fix the problem, but we don't want to restrict F to 'static types, so another solution is needed.

The problem is that dyn Foo is implicitly a dyn Foo + 'static. But what does that mean? Well, when you a trait object there's actually an instance of a concrete type that has been erased into that trait object. That type however may have a lifetime, meaning you can't let the instance life forever, you can only do that until the lifetime is valid. Thus trait objects actually carry a lifetime, like dyn Trait + 'a, where the 'a represents how much the instance behind the trait object can live.

Now, how does this connect to your problem? Well, you're putting an F, which could have any lifetime, inside a dyn Foo + 'static, which requires the type to have a 'static lifetime (with this I mean that F: 'static holds). Hence the suggestion in the error, which would fix your problem, but would also be overly restricting.

There's however an alternative: changing the return type to allow non-'static types, and linking the lifetime of the input to the output. This can be done by introducing a lifetime (let's call it 's), and changing the return type of Box<dyn Foo + 's>. This lifetime doesn't necessarily have to be 'static, so you can get rid of the 'static bound. However you still have the requirement that whatever you put inside the dyn Foo + 's must satisfy the lifetime 's, so this needs s F: 's bound.

You might ask why you need to annotate both the input and the output types with that lifetime. That's because you're linking them. You're taking the input and saying "let's call 's its lifetime", and then you go on the output and say "this is only valid for the lifetime 's, because it somehow contains and instance of the input". Note that this valid in general: a single lifetime parameter doesn't do anything, its power comes only from linking it to different parameters.

And here's the fixed code:

trait Foo {}

fn make_dyn_foo<'s, F: Foo + 's>(f: F) -> Box<dyn Foo + 's> {
    Box::new(f)
}

Your case is pretty similar to this, except you have two trait objects, and you need to change both of them.

4 Likes

Thanks. So, the issue is that the default constraint for trait objects regarding lifetime is 'static, which means, the concrete type C of the instance of the trait object should satisfy 'static(i.e. C: 'static). If we explicitly specify the lifetime, the specified lifetime will overwrite the default ones. In the closure type F, the type V of the captured entity is a inhere type of F, when we say Box<dyn Fn(T)->U + 's>, that means F: 's and V: 's should be satisfied.

Both F and V can live longer than 's, the compiler will covariant them to make their lifetimes agree on the same 's. If either of them would disagree with the contract, the compiler would give an error.

fn test_lifetime<'s,F:Fn() +'s>(f:F) -> Box<dyn Fn() +'s>
{
    Box::new(f)
}
fn main(){
    let r;
    {
        let i = 0;
        let f = ||{
            let rf = &i;
        };
        r = test_lifetime(f);
        drop(r);  // #1
    }
}

Comment #1 would destroy the contract.

I believe you mean this:

-let i = 0;
+let i = vec![0]; // just any type not implemented with Copy trait so that you can drop it
...
-drop(r);  // #1
+drop(i);  // #1

I just want to demonstrate that the return value r and the input value f can shrink to the same lifetime 's in the contract. If we comment #1, the lifetime of r cannot shrink to the inner block, which would destroy the contract in the function signature. The contract in the function signature can make the compiler pass the check of the function definition, which is the reason why the error occurs in the first step of my original example. This is my understanding of the answers.

I'll just note that the default implicit lifetime for a dyn Trait is not always 'static; it is sometimes 'static, sometimes another lifetime or inferred lifetime in scope, and sometimes free to be inferred.

When it comes to function signatures though, you can usually just think of it as

  • &'_a dyn Trait is &'_a dyn Trait + '_a (even if '_a is elided)
  • Others like Box<dyn Trait> are Box<dyn Trait + 'static>

RFC 0599, RFC 1156, RFC 2093

1 Like

For default lifetime of trait objects, the relevant document is a bit obscure, see Ch19: Clear up rules around lifetimes and trait objects · Issue #624 · rust-lang/book · GitHub

Most nuanced parts of the language [1] are not well documented [2], and trait object lifetimes are quite nuanced.


  1. in contrast with the standard library ↩︎

  2. much less normatively specified ↩︎

1 Like

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.