Reputation: 105
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 | | }
| |_^
Upvotes: 3
Views: 154
Reputation: 88576
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.
Upvotes: 4
Reputation: 105
The infered code seems to be:
fn get<F : for<'inp> Fn(&'inp[u8]) -> u8>(f: F) -> u8 {
f(&[1,2,3])
}
Upvotes: 0