The following code works fine:
fn get<F: Fn(&[u8]) -> u8>(f: F) -> u8 {
f(&[1, 2, 3])
}
However, when I add explicit lifetime information to it, it doesn't:
fn get<'inp, F: Fn(&'inp [u8]) -> u8>(f: F) -> u8 {
f(&[1, 2, 3])
}
What lifetime does the compiler infer in the working code?
I'm using Rust 1.18.0.
The error message is:
error: borrowed value does not live long enough
--> test.rs:4:8
|
4 | f(&[1, 2, 3])
| ^^^^^^^^^ does not live long enough
5 | }
| - temporary value only lives until here
|
note: borrowed value must be valid for the lifetime 'inp as defined on the body at 3:49...
--> test.rs:3:50
|
3 | fn get<'inp, F: Fn(&'inp [u8]) -> u8>(f: F) -> u8 {
| __________________________________________________^
4 | | f(&[1, 2, 3])
5 | | }
| |_^
Lifetimes in trait bounds are a bit special and the Fn family of traits has a special lifetime elision rule. We'll dive into that, but first, here it the correct explicitly annotated version:
fn get<F: for<'inp> Fn(&'inp [u8]) -> u8>(f: F) -> u8 {
f(&[1, 2, 3])
}
Oh gosh, what is this for<'inp> doing there? It's a so called higher ranked trait bound (HRTB) and it's used here to make 'inp universally quantiefied in regards to f. In order to fully understand that, we need to understand a bit of theory.
Let's take a look at an easy example:
fn bar<'a>(x: &'a u8) {}
Here, bar() is generic of the lifetime 'a. The syntax above reads: "choose any 'a and there is a bar() that will work with 'a". This means that we can choose any 'a we want, and bar() works! Who are "we"? "We" are the caller -- the one calling bar. This will be important later: the caller chooses the generic parameters. We can call bar() with a &'static u8 as well as with a reference that doesn't live as long.
Now you might ask: are there situations where the caller doesn't choose the generic parameter, but someone else does? Yes, there are! Sadly, it's a bit more difficult to understand, because it doesn't occur too often in today's Rust code. But let's try:
trait Bar<'a> {
fn bar(&self, x: &'a u8);
}
This is similar to the bar() function above, but now the lifetime parameter is defined on the trait, not the function. Let's use the trait:
fn use_bar<'a, B: Bar<'a>>(b: B) {
let local = 0u8;
b.bar(&local);
}
This doesn't compile, printing the same error as above. Why? The method b.bar() expects a reference of lifetime 'a. But who chooses 'a here? Exactly: the caller -- the caller of use_bar(), not the caller of bar()! So the caller of use_bar() could choose the 'static lifetime; in that case, it's easy to see that our &local doesn't fulfill the lifetime requirements.
Note that the caller of use_bar() chooses 'a as well as B. Once use_bar() is instantiated, B is a fixed type and B::bar() works only for one specific lifetime. This means the caller of bar() can't choose the lifetime, but bar() itself chose it!
What do we want instead? We want use_bar() to choose the lifetime of the bar() call. And we can do that with the for<> syntax:
fn use_bar<B: for<'a> Bar<'a>>(b: B) {
let local = 0u8;
b.bar(&local);
}
This works. What we say here is: "for any lifetime parameter 'a, B has to implement the trait Bar<'a>". Instead of: "there needs to exist a lifetime parameter 'a for which B implements Bar<'a>". It's all about who chooses which parameter.
Let's use the real names for it:
To return to your example:
fn get<'inp, F: Fn(&'inp [u8]) -> u8>(f: F) -> u8 {
f(&[1, 2, 3])
}
Here we have the same problem as above: the lifetime parameter of f is existentially quantified. The caller of f cannot choose the lifetime parameter. We can fix that with the for<> syntax as shown above.
When you omit the lifetimes:
fn get<F: Fn(&[u8]) -> u8>(f: F) -> u8 {
f(&[1, 2, 3])
}
The Rust compiler will do something special for the Fn family of traits. Your F: Fn(&[u8]) desugars to F: for<'a> Fn<(&'a [u8],)>. If you use Fn* traits with parameters that involve lifetimes, those lifetimes are automatically universally quantified, because that's usually what you want with higher order functions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With