Passing `Box<dyn FnOnce>` to function becomes 'static lifetime bound?

I have a function square which I made into a FnMut by having some state it modifies, namely applied_count.

This function can be applied with transform(vec![1,3,4], vec![func]).
where transform only takes FnOnce because thats the most less restrictive function trait it needs. However there is a borrow problem which I dont understand.


n main() {
    let numbers = vec![1, 2, 5, 9];

    let mut applied_count = 0;
    let square = |x: &[i32]| -> Vec<i32> {
        applied_count += 1;
        x.iter().map(|x| x * x).collect()
    };

    let a: Box<dyn FnOnce(&[i32]) -> Vec<i32>> = Box::new(square);
    let res = transform(&numbers, vec![a]);

    print!("Result: {res:?}, {applied_count}")
}

fn transform(numbers: &[i32], funcs: Vec<Box<dyn FnOnce(&[i32]) -> Vec<i32>>>) -> Vec<i32> {
    let mut res = vec![];
    let mut current = numbers;

    for f in funcs {
        res = f(current);
        current = &res;
    }

    return res;
}

The compiler somehow tells me that the lifetime constraint of FnOnce becomes + 'static:

note: due to object lifetime defaults, 
`Box<dyn for<'a> FnOnce(&'a [i32]) -> Vec<i32>>` actually means 
`Box<(dyn for<'a> FnOnce(&'a [i32]) -> Vec<i32> + 'static)>`
  • What does it mean? Does it mean the &mut i32 captured variable must outlive 'static -> meaning itself must be `'static'.
  • Why does that happen when the function transform is called and not otherwise when I call a(&numbers)?
  • How can I pass the the list of closures to the transform (its a sink, for all closures the lifetime ends at f(current) (I guess)) and later access applied_count?
error[E0597]: `applied_count` does not live long enough
  --> exercises/closures/src/bin/03-solution.rs:11:9
   |
9  |     let mut applied_count = 0;
   |         ----------------- binding `applied_count` declared here
10 |     let square = |x: &[i32]| -> Vec<i32> {
   |                  ----------------------- value captured here
11 |         applied_count += 1;
   |         ^^^^^^^^^^^^^ borrowed value does not live long enough
...
15 |     let a: Box<dyn FnOnce(&[i32]) -> Vec<i32>> = Box::new(square);
   |                                                  ---------------- cast requires that `applied_count` is borrowed for `'static`
...
19 | }
   | - `applied_count` dropped here while still borrowed
   |
   = note: due to object lifetime defaults, `Box<dyn for<'a> FnOnce(&'a [i32]) -> Vec<i32>>` actually means `Box<(dyn for<'a> FnOnce(&'a [i32]) -> Vec<i32> + 'static)>`

The default lifetime for a trait object is often 'static. You can override this by adding an explicit lifetime on the function parameter as follows:

        fn transform(
            numbers: &[i32],
            funcs: Vec<Box<dyn FnOnce(&[i32]) -> Vec<i32> + '_>>,
        ) -> Vec<i32> {

I think it is a fairly common problem when using trait objects.

Edit: You don't have to declare the lifetime, you can just add + '_ to the trait object type to force it to be inferred rather than use the 'static default.


A simplification is to use a type alias for this, since it is so complex.

type TransformFuncs<'a> = Box<dyn FnOnce(&[i32]) -> Vec<i32> + 'a>;

fn main() {
    let numbers = vec![1, 2, 5, 9];

    let mut applied_count = 0;
    let square = |x: &[i32]| -> Vec<i32> {
        applied_count += 1;
        x.iter().map(|x| x * x).collect()
    };

    let a: TransformFuncs<'_> = Box::new(square);
    let res = transform(&numbers, vec![a]);

    print!("Result: {res:?}, {applied_count}")
}

fn transform(numbers: &[i32], funcs: Vec<TransformFuncs<'_>>) -> Vec<i32> {
    let mut res = vec![];
    let mut current = numbers;

    for f in funcs {
        res = f(current);
        current = &res;
    }

    res
}

Sorry for all the edits. Although with the type alias you do not have to add the <'_> to TransformFuncs to cause the lifetime to be inferred, I recommend setting the following rustc lint to force you to do this to avoid confusion.

In code at the top of the root module for your crate:

#![deny(elided_lifetimes_in_paths)]

Or in Cargo.toml:

[lints.rust]
elided_lifetimes_in_paths = "deny"
7 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.