fn main() {
let f = Foo(Box::new(|s, _| s.bytes()));
}
struct Foo<U>(Box<dyn for<'a> Fn(&'a str, &str) -> U + 'a>);
error[E0261]: use of undeclared lifetime name `'a`
--> src\main.rs:16:56
|
16 | struct Foo<U>(Box<dyn for<'a> Fn(&'a str, &str) -> U + 'a>);
| - ^^ undeclared lifetime
| |
| help: consider introducing lifetime `'a` here: `'a,`
The compiler suggest me to add the lifetime declaration to the definition of Foo, but I think there is no necessity.
If I modify it to this, the program can be compiled.
use std::str::Bytes;
struct Foo(Box<dyn for<'a> Fn(&'a str, &str) -> Bytes<'a>>);
So I think Foo indeed does not need a lifetime declaration. How to remove the lifetime declaration in the generic type case?
The scope of the 'a introduced by the for keyword is limited to the Fn. The final 'a is unrelated to that, hence needs to be declared somewhere, which would be at the generic parameter list of Foo.
To be clear, you are not actually adding 'a to U here. The scoping is different. What you wrote is equivalent to this:
struct Foo<U>(Box<dyn 'a + for<'a> Fn(&'a str, &str) -> U>);
The reason that your code doesn't compile if you remove the + 'a is that the closure in main is trying to return the string, which would give it this siganture:
for<'a> fn(&'a str, &str) -> std::str::Bytes<'a>
However, your box defines U first, and then 'a, so U may not depend on 'a.
I don't there's any easy way to fix the definition of Foo to allow it. You can't "add" a lifetime to U after defining it, and you cannot define U inside the for<'a>.
If I define Foo as follows, it indeed could store |s| s.bytes(), but cannot execute it.
struct Foo<'a, U: 'a>(Box<dyn Fn(&'a str) -> U>);
fn main() {
let f: Foo<Bytes<'_>> = Foo(Box::new(|s| s.bytes()));
let k = "abc".to_string();
f.0(&k);
}
error[E0597]: `k` does not live long enough
--> src\main.rs:15:9
|
15 | f.0(&k);
| ^^ borrowed value does not live long enough
16 | }
| -
| |
| `k` dropped here while still borrowed
| borrow might be used here, when `f` is dropped and runs the destructor for type `Foo<'_, std::str::Bytes<'_>>`
|
= note: values in a scope are dropped in the opposite order they are defined
Indeed. Since 'a is a lifetime that is annotated on Foo, the lifetime must contain the region in which the Foo is alive. However, the destructor of k runs before Foo is destroyed, so you cannot borrow k for the lifetime 'a — values cannot be destroyed before the borrow ends.
In this specific instance you can fix it by swapping f and k so that k is destroyed after f, but I highly suspect that making the argument outlive the Foo would not be possible in your actual use-case.
fn main() {
let k = "abc".to_string();
let f: Foo<Bytes<'_>> = Foo(Box::new(|s| s.bytes()));
f.0(&k);
}
Type parameters in Rust must resolve to a single type; they are not type constructors. So U alone cannot represent a borrowed return with an arbitrary lifetime, as each distinct lifetime is a distinct type.
Bytes<'_> works as it is a type constructor.
Your latest code sometimes works because you have restricted yourself to a specific lifetime.
You may be able to work around it with indirection through a trait (which acts as a type constructor), which I can demonstrate once at a non phone.
// In some cases I think this can be avoided by relying on the Fn traits
// instead, where the return type is an associated type.
Here's that approach, with a different trade-off (rustc fails to infer a higher-ranked closure; see the links in the code for more information and work-arounds).
And incidentally, #[unboxed_closures] removes the need for custom traits, but doesn't solve the inference issue. (The custom traits are needed because the Fn(&'a str, &str) -> Bytes<'a> sugar does not permit eliding the associated return type, which isn't so sweet in this case.)
In my modified demo, I can't explain this phenomenon:
fn main() {
// Another downside is that Rust fails to see through the indirection
// let f = Foo(Box::new(|s, _| s.bytes()));
// Success
let f=my_func::<ReturnsBytes,_>(|s:&str, _| s.bytes());
// Cannot compile
//let f = Foo::<ReturnsBytes>::my_func(|s:&str, _| s.bytes());
}
If I let the code I commented out to be compiled, rustc will throw this error:
error[E0271]: type mismatch resolving `for<'a, 'r> <[closure@src/main.rs:63:42: 63:63] as FnOnce<(&'a str, &'r str)>>::Output == <ReturnsBytes as MaybeBorrowed<'a>>::Return`
--> src/main.rs:63:13
|
63 | let f = Foo::<ReturnsBytes>::my_func(|s:&str, _| s.bytes());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `std::str::Bytes`
|
= note: expected associated type `<ReturnsBytes as MaybeBorrowed<'_>>::Return`
found struct `std::str::Bytes<'_>`
= help: consider constraining the associated type `<ReturnsBytes as MaybeBorrowed<'_>>::Return` to `std::str::Bytes<'_>` or calling a method that returns `<ReturnsBytes as MaybeBorrowed<'_>>::Return`
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
note: required by a bound in `Foo::<U>::my_func`
--> src/main.rs:51:40
|
49 | fn my_func<F>(func:F)
| ------- required by a bound in this
50 | where
51 | F:for<'a> Fn(&'a str, &str) -> <U as MaybeBorrowed<'a>>::Return
|