tarski
tarski

Reputation: 330

the parameter type may not live long enough (again?) (in closure)

In trying to compile the code

fn make<DomainType>(foo: fn(DomainType) -> ()) -> Box<dyn Fn(DomainType) -> ()> {                                                                                                                                                 
    let foo_clone = foo.clone();                                                                                                                                                                                                  
    let bar = move |input: DomainType| {foo_clone(input)};                                                                                                                                                                        
    Box::new(bar)                                                                                                                                                                                                                 
}                
                                                                                                                                                                                                                                  
fn main() {}                                                                                                                                                                                                                      

The compiler gives

error[E0310]: the parameter type `DomainType` may not live long enough
 --> src/main.rs:4:5
  |
4 |     Box::new(bar)
  |     ^^^^^^^^^^^^^ ...so that the type `[closure@src/main.rs:3:15: 3:58]` will meet its required lifetime bounds
  |
help: consider adding an explicit lifetime bound...
  |
1 | fn make<DomainType: 'static>(foo: fn(DomainType) -> ()) -> Box<dyn Fn(DomainType) -> ()> {
  |                   +++++++++

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

I have been reading through similar questions Parameter type may not live long enough? and The compiler suggests I add a 'static lifetime because the parameter type may not live long enough, but I don't think that's what I want

But in both of those examples, the boxed trait is what needs the added lifetime. In this case it's apparently just the DomainType of the closure trait. I'm quite baffled what to make of this.

My understanding of lifetime variables in function definitions is that they connect the lifetime of the returned type (which I can understand is implicitly static here, or at least the thing inside the box is) to those (perhaps elided) lifetimes of the function parameters. But no function parameter here is a DomainType.

Upvotes: 1

Views: 740

Answers (2)

Todd
Todd

Reputation: 5385

Could Higher Rank Trait Bounds be what you're looking for?

fn make<F, DomainType>(foo: F) -> Box<dyn Fn(DomainType)>
where
    for<'r> F: Fn(DomainType) + Clone + 'r
{
    let foo_clone = foo.clone();
    let bar = move |input: DomainType| {foo_clone(input)};
    Box::new(bar)
} 

If the purpose of the function is to consume a closure and return it in a Box, I don't think you need the Clone trait. In fact, it doesn't even need to be nested in a new closure. Just directly box it.

fn make<F, DomainType>(foo: F) -> Box<dyn Fn(DomainType)> 
where 
    for<'r> F: Fn(DomainType) + 'r 
{
    Box::new(foo)
} 

Unless you want to pass in a reference to a closure that doesn't get consumed:

fn make<F, DomainType>(foo: &F) -> Box<dyn Fn(DomainType)> 
where 
    for<'r> F: Fn(DomainType) + Clone + 'r 
{
    Box::new(foo.clone())
} 

Upvotes: 0

Finomnis
Finomnis

Reputation: 22476

DISCLAIMER: This question goes very deep into Rust compiler specifics, and I'm not 100% sure about everything I say here. Take everything with a grain of salt.


The problem here is that Box<T> means that T is 'static if no lifetime is annotated.

A generic, however, does not mean that. A generic could contain any kind of lifetime. To match the two, you need an explicit lifetime annotation.

Solution 1

You could annotate 'static to the generic, like this:

fn make<DomainType: 'static>(foo: fn(DomainType)) -> Box<dyn Fn(DomainType)> {
    Box::new(foo)
}

That, however, would create problems if you do actually encounter the case where the generic carries a lifetime:

fn my_func(x: &str) {
    println!("my_func: {}", x);
}

fn main() {
    let x = "abc".to_string();

    let f = make(my_func);
    f(&x);
}
error[E0597]: `x` does not live long enough
  --> src/main.rs:13:7
   |
13 |     f(&x);
   |     --^^-
   |     | |
   |     | borrowed value does not live long enough
   |     argument requires that `x` is borrowed for `'static`
14 | }
   | - `x` dropped here while still borrowed

Solution 2

The proper way here is to solve the initial mismatch by telling the compiler that the lifetimes attached to the generic match the lifetimes contained in the Box.

Like this:

fn make<'a, DomainType: 'a>(foo: fn(DomainType)) -> Box<dyn Fn(DomainType) + 'a> {
    Box::new(foo)
}

And it now works:

fn my_func(x: &str) {
    println!("my_func: {}", x);
}

fn main() {
    let x = "abc".to_string();

    let f = make(my_func);
    f(&x);
}
my_func: abc

Problems

There are still problems with this. I'm unsure how/if they are solvable, though.

The problem is that the resulting Box object will always be bound to a specific lifetime. Look for example at this:

fn make<'a, DomainType: 'a>(foo: fn(DomainType)) -> Box<dyn Fn(DomainType) + 'a> {
    Box::new(foo)
}

fn my_func(x: &str) {
    println!("my_func: {}", x);
}

fn make2() -> Box<dyn Fn(&str)> {
    Box::new(|x| println!("closure: {}", x))
}

fn main() {
    let x = "abc".to_string();

    let f = make(my_func);
    let f2 = make2();

    let fs = [f2, f];
}
error[E0308]: mismatched types
  --> src/main.rs:19:19
   |
19 |     let fs = [f2, f];
   |                   ^ one type is more general than the other
   |
   = note: expected trait object `dyn for<'r> Fn(&'r str)`
              found trait object `dyn Fn(&str)`

f2 here takes all objects that can be borrowed. f only takes objects that all can be borrowed with the same lifetime, as 'a resolves when make is called, not when f is executed.

Another example is this:

fn make<'a, DomainType: 'a>(foo: fn(DomainType)) -> Box<dyn Fn(DomainType) + 'a> {
    Box::new(foo)
}

fn my_func(x: &str) {
    println!("my_func: {}", x);
}

fn make2() -> Box<dyn Fn(&str)> {
    Box::new(|x| println!("closure: {}", x))
}

fn main() {
    let f = make(my_func);
    let f2 = make2();

    {
        let x = "abc".to_string();
        f2(&x); // Works
        f(&x); // Fails to compile
    }
}
error[E0597]: `x` does not live long enough
  --> src/main.rs:20:11
   |
20 |         f(&x); // Fails to compile
   |           ^^ borrowed value does not live long enough
21 |     }
   |     - `x` dropped here while still borrowed
22 | }
   | - borrow might be used here, when `f` is dropped and runs the destructor for type `Box<dyn Fn(&str)>`
   |
   = note: values in a scope are dropped in the opposite order they are defined

I don't think this is solvable, though. I think generics can't carry generic lifetimes, they can only carry specific lifetimes. And said specific lifetime gets resolved at the point where make() is called.

Although a proper explanation would require someone with a deeper Rust compiler understanding than me. If something like this exists, I'd be interested in it as well. (something like DomainType: for<'a> or similar)

I can see that one of the example's output says it would require a dyn for<'r> Fn(&'r str). I don't know, however, if something like this is annotatable by hand or if it's something internal to the compiler. But that would be what is required - instead of Box<dyn Fn(DomainType) + 'a>, we would need something like Box<dyn for <'r> Fn(DomainType + 'r)>. Again, I don't know if/how this is annotatable, and if it is, I'd also be interested in it.

Upvotes: 4

Related Questions