A higher-order lifetime ceases to be so
So, all this stems from a very basic question, or observation, about a probably hand-wave but critical detail here: what is the signature of f = first
?
-
the
first
closure is defined as|vals: &[i32]| vals[0]
, which means its signature is that of:Fn(&[i32]) -> i32
that is:
for<'any> Fn(&'any [i32]) -> i32
-
the
f
argument must have a signature ofFn(A) -> B
, meaning there must exist typesA
, andB
so thatimpl Fn(&[i32]) -> i32 : Fn(A) -> B
.We'd be tempted to say that
B = i32
, andA = &[i32]
fit the situation, except&[i32]
is not a type, not a complete type I mean: there is a missing lifetime parameter in there.
So we end up with: A = &'? [i32]
, and thus:
// find `'?` so that:
impl for<'any> Fn(&'any [i32]) -> i32
: Fn(&'? [i32]) -> i32
It turns out that while any choice of '?
is satisfiable here, e.g., A = &'static [i32]
, it will nonetheless be a fixed lifetime choice
So we end up with:
// A = &'inferred [i32]
let first_plus_one: impl Fn(&'inferred [i32]) -> i32 = compose(first, increment);
So, already, notice how your resulting composed closure has lost the for<'any>
universal quantification of its lifetime / its "being lifetime-generic", and, instead, wants an input with some very specific 'inferred
duration.
A lifetime-infected type
If we now look at your compose
function for your specific case, its signatures ends up being:
fn compose<'inferred, 'f, 'g, 'ret>(
f: impl 'f + Fn(&'inferred [i32]) -> i32,
g: impl 'g + Fn(i32) -> i32,
) -> impl 'ret + Fn(&'inferred [i32]) -> i32
where
'inferred : 'f, // `f` captures `'inferred`, so for `f` to be usable, `'inferred` must not have ended yet
'f : 'ret, // ret value catpures `f`, so for it to be usable, `f` must be usable too.
'g : 'ret, // ret value captures `g`, so for it to be usable, `g` must be usable too.
- Intuitively,
'ret
will be the intersection of the'inferred
,'f
, and'g
regions.
The closures in your example don't capture anything (but the fixed 'inferred
lifetime), so we can further simplify all this down to:
fn compose<'inferred>(
f: impl 'inferred + Fn(&'inferred [i32]) -> i32,
g: impl 'static + Fn(i32) -> i32,
) -> impl 'inferred + Fn(&'inferred [i32]) -> i32
And now, the interesting thing is, we end up with:
-> impl ... 'inferred ...
That is, an existential lifetime-infected type (note: for dyn
this would be the case as well).
Since 'inferred
is part of this type, for (instances of) the type to be usable, 'inferred
must not have ended yet.
-
This is a classic rule of Rust. For instance, consider:
let mut s; { let local = String::from("..."); s = Some(&local); s = None; } drop(s); // Error, `local` already dropped.
This will fail because the type of
s
,Option<&'inferred String>
, requires that'inferred
not dangle when wedrop(s);
, since that constitutes a usage of an instance of the type (and the snippet fails because'inferred
must fit within the lifetime of the borrow oflocal
for the assignment to be valid, butlocal
dies too soon).
And yet, both these things, alone, would not cause your compile error, since you do not drop
or otherwise explicitly use increment_then_double
after vals
is dropped.
The last issue, and which ultimately causes the compile error, is that:
An -> impl ...
type has conservative drop glue / no NLL
That is, if we look at our -> impl ... 'inferred ...
existential return type, by virtue of being so (type-erased / implementation-agnostic yadda yadda), there could be implementations of this type where instances of it could be using 'inferred
within the implicit drop glue, as far as rust is concerned.
That is, for -> impl ...
types, as well as for dyn ...
types, "going out of scope" constitutes a use, which therefore requires the lifetimes not to dangle:
let first_plus_one = compose(first, increment); // may have / conservatively has drop glue.
{
let vals = vec![5, 8, 9];
let result = first_plus_one(&vals); // `'vals : 'inferred` for this call to be OK.
assert_eq!(6, result);
} // drop(vals);
} // drop(first_plus_one); /* Conservative drop glue means `'inferred` must be valid here! */
Hence the error.