Struct with function pointer with data passing through

I have trouble to find a solution to the lifetime issue of function parameters not living long enough.

In the following example I measure the runtime (very broadly, I know) and have a testcase struct which just stores the function and its runtime. The data for the function passes just through and is not required after the function has run, but the compiler complains it does not live long enough.

I also wonder, why I cannot declare the lifetime of the function 'static, as the function itself does not change.

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}
struct Testcase<'a, 'b> {
    // Does not work. Why? Function is compiled and address is fixed during runtime, or not?
    // func: &'static dyn Fn(&'b [Point]) -> Vec<&'b Point>, 
    func: &'a dyn Fn(&'b [Point]) -> Vec<&'b Point>,
    name: &'static str,
    duration_ms: f64,
}

impl<'a,'b> Testcase<'a,'b> {
    pub fn do_test(&'a self, points: &'b [Point]) -> Vec<&'b Point> {
        (self.func)(points)
    }
}

fn gift_wrapping(points: &[Point]) -> Vec<&Point> {
    let mut vec = Vec::new();
    vec.push(&points[0]);
    vec
}

fn graham_scan(points: &[Point]) -> Vec<&Point> {
    let mut vec = Vec::new();
    vec.push(&points[0]);
    vec
}

fn main() {
    let mut testcases = [
        Testcase {
            func: &gift_wrapping,
            name: "gift wrapping",
            duration_ms: 0.0,
        },
        Testcase {
            func: &graham_scan,
            name: "graham scan",
            duration_ms: 0.0,
        },
    ];

    // This works, but I explicitly want new data for each test case
    // let points = get_random_points();
    for testcase in &mut testcases {
        let points = get_random_points();
        let convex_hull = testcase.do_test(&points); // <-- borrowed value does not live long enough
        testcase.duration_ms = 42.0;
        println!("{} = {}, first: {:?}", testcase.name, testcase.duration_ms, convex_hull[0]);
    }
}

fn get_random_points() -> Vec<Point> {
    let mut points = Vec::new();
    let p = Point { x: 10, y: 10 };
    points.push(p);
    points
}

TLDR: Use function pointers for this use case. The type syntax mirrors the function declaration signature, minus argument names.

struct Testcase {
    func: fn(&[Point]) -> Vec<&Point>,
    name: &'static str,
    duration_ms: f64,
}

impl Testcase {
    // note changed signature
    pub fn do_test<'b>(&self, points: &'b [Point]) -> Vec<&'b Point> {
        (self.func)(points)
    }
}

// ...
    let mut testcases = [
        Testcase {
            func: gift_wrapping, // <-- no `&`

A couple things...

&'a self

That's an anti-pattern. Not the worst in this case, but unneeded. Also not the source of your problems.

    for testcase in &mut testcases {
        let points = get_random_points();
        let convex_hull = testcase.do_test(&points); // <-- borrowed value does not live long enough

testcases has the type TestCase<'a, 'b>, and that 'b has to last for as long as testcases is around. Then you borrow the local-to-the-loop points for 'b each time through the loop. That's the source of your borrow error.

There might be some dance to get this to work with a function that takes a single lifetime,[1] but if so, it's not worth it. Instead you can use a HRTB to indicate that your dyn Fns should be callable with any lifetime.

//             vvvv just the one lifetime param now
struct Testcase<'a> {
    // Short for `&'a dyn for<'any> Fn(&'any [Point]) -> Vec<&'any Point>
    func: &'a dyn Fn(&[Point]) -> Vec<&Point>,
    // You could use `&'static dyn Fn(&[Point]) -> Vec<&Point>` too now
    name: &'static str,
    duration_ms: f64,
}

impl Testcase<'_> {
    // Tie the lifetimes together here instead
    //            vvvv                 vv                  vv
    pub fn do_test<'b>(&self, points: &'b [Point]) -> Vec<&'b Point> {
        (self.func)(points)
    }
}

// Does not work. Why? Function is compiled and address is fixed during
//  runtime, or not?
// func: &'static dyn Fn(&'b [Point]) -> Vec<&'b Point>, 

Not necessarily -- dyn Fn(..) types include capturing closures. They might capture something that's not 'static[2] and don't have to have a static address (could be on the stack).

That said, even function pointers (which cannot be capturing) that take a specific lifetime do not meet a 'static bound:[3]

fn example(_: &str) {}

fn witness<T: 'static>(_: &mut T) {}

fn foo<'a>() {
    let mut fp: fn(&'a str) = example;
    // fails: type annotation requires that `'a` must outlive `'static`
    witness(&mut fp);
}

It's due to how well-formed types are defined; you simply can't have a non-'static lifetime behind a &'static _ ref.[4] This can be surprising in the case of function pointers. Maybe they'll get a special carveout some day, but I think there are some subtleties around the Any trait. Here's an issue about it.

Incidentally, non-capturing closures can be coerce to function pointers.


  1. or not, I didn't try ↩︎

  2. well, your 'static annotation would prevent that, but keep reading ↩︎

  3. The &mut is in this example so that fn(&'a str) doesn't coerce to fn(&'static str) ↩︎

  4. It's okay to have a higher-ranked for<'a> binder, like one of the playgrounds above shows. ↩︎

1 Like

Thank you for taking the time to write this great and elaborate answer, it really made things clear for me.