If I define function composition as follows (similar to numerous other definitions):
fn compose<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> C
where
F: Fn(A) -> B,
G: Fn(B) -> C,
{
move |x| g(f(x))
}
then the following works fine:
fn main() {
let increment = |x: i32| x + 1;
let double = |x: i32| x * 2;
let increment_then_double = compose(increment, double);
let result = increment_then_double(3);
assert_eq!(8, result);
}
However, if I have the initial input to a composition be a slice:
fn main() {
let increment = |x: i32| x + 1;
let first = |vals: &[i32]| vals[0];
let first_plus_one = compose(first, increment);
let vals = vec![5, 8, 9];
let result = first_plus_one(&vals);
assert_eq!(6, result);
}
I get the following error:
error[E0597]: `vals` does not live long enough
--> src/main.rs:40:33
|
40 | let result = first_plus_one(&vals);
| ^^^^^ borrowed value does not live long enough
...
43 | }
| -
| |
| `vals` dropped here while still borrowed
| borrow might be used here, when `first_plus_one` is dropped and runs the destructor for type `impl Fn(&[i32]) -> i32`
|
= note: values in a scope are dropped in the opposite order they are defined
For more information about this error, try `rustc --explain E0597`.
I don't really get what's happening here. It seems like the closure first_plus_one
is somehow "capturing" &vals
, but I don't know why it would hold a reference to &vals
past the execution of the closure.
I've done a lot of searching and gotten nowhere, and a bunch of us spent over an hour in my Twitch live stream this morning wrestling with it to no particular avail. We have found two "fixes", but neither is great and neither really explains what's happening.
First, if we switch the declaration order of vals
and first_plus_one
, then it compiles and runs:
let vals = vec![5, 8, 9];
let first_plus_one = compose(first, increment);
This isn't really a workable solution, though, as we want to be able to construct functions like first_plus_one
up front, and then apply them later in various settings.
Second (& weirdly), if you move the definition of first_plus_one
out into a "true" function:
fn first_plus_one_fn(vals: &[i32]) -> i32 {
compose(first, increment)(vals)
}
then everything works fine. That's workable as a solution, but begs the question as to what the problem actually is. In particular, I would like to think of the first_plus_one
closure and the first_plus_one_fn
function as being semantically equivalent, but clearly they aren't, at least not to the compiler.
Anyone able to help shed some light on what's going on here?
Many thanks in advance – Nic