Function with lifetimes used as argument

I fail to use function as argument when lifetimes need to be set. Below an example (playground):

use std::fmt::{Debug, Display};

#[derive(Default)]
pub struct Context {}

fn function_1<'a>(_: &'a Context,a: &'a mut String) -> &'a String {
    a.push_str("1");
    a
}

fn function_2<'a>(_: &'a Context, b : &'a mut usize) -> &'a usize {
    *b += 1;
    b
}

fn use_function_1_or_function_2<'a, D>(
    d: D,
    f: fn(&'a Context,&'a mut D) -> &'a D,
) -> String
    where D: Display
{
    let context = Context::default();
    let mut d = d;
    let d = f(&context,&mut d);
    format!("{}", d)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn simple_test() {
        let string = "hello".to_string();
        let result = use_function_1_or_function_2(string, function_1);
        assert_eq!(result, "hello1");
    }
}

Here the below error:

error[E0597]: `context` does not live long enough
  --> src/generic_function_as_parameter.rs:25:15
   |
17 | fn use_function_1_or_function_2<'a, D>(
   |                                 -- lifetime `'a` defined here
...
25 |     let d = f(&context,&mut d);
   |             --^^^^^^^^--------
   |             | |
   |             | borrowed value does not live long enough
   |             argument requires that `context` is borrowed for `'a`
26 |     format!("{}", d)
27 | }
   | - `context` dropped here while still borrowed

I don't understand why it is a problem for the compilation that context is dropped at the end of the method use_function_1_or_function_2, we don't use it anymore.

I suppose that the compilator doesn't know the use of reference arguments because of the function is a dynamic callback.

There is some way to do it?

You can use higher-rank trait bounds (HRTBs) to get Fn types with lifetimes to compile like this:

use std::fmt::{Debug, Display};

#[derive(Default)]
pub struct Context {}

fn function_1<'a>(_: &'a Context,a: &'a mut String) -> &'a String {
    a.push_str("1");
    a
}

fn function_2<'a>(_: &'a Context, b : &'a mut usize) -> &'a usize {
    *b += 1;
    b
}

fn use_function_1_or_function_2<D, F>(
    d: D,
    f: F,
) -> String
    where 
        D: Display,
        for <'a> F: Fn(&'a Context, &'a mut D) -> &'a D
{
    let context = Context::default();
    let mut d = d;
    let d = f(&context,&mut d);
    format!("{}", d)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn simple_test() {
        let string = "hello".to_string();
        let result = use_function_1_or_function_2(string, function_1);
        assert_eq!(result, "hello1");
    }
}

Playground.

1 Like

FYI Steal the same answer from @H2CO3

Generic type and lifetime (and const) arguments are chosen by the caller. Here, you want the lifetime to be chosen by your own function body instead. You can't generate a reference that is valid at least as long as your function body from within your function body (not to a local variable, at least), so no matter what lifetime the generic is substituted with, your local's address won't satisfy it.

You need a HRTB[1] instead.


  1. I know the usual link is to the nomicon, but the Reference book describes HRTB in a user-friendly way. ↩ī¸Ž

3 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.