1

I'm writing a algorithm testbench to compare performance in Rust.

I want to store a bunch of function for an algorithm in a struct, and apply those functions to some data. When I call the function by reference, which stored in the struct, I couldn't figure out the lifetime.

struct Alg<'a, 'b, 'c> {
    alg1: &'c Fn(&'a A<'a>, &'b B<'b>) -> usize,
    alg2: &'c Fn(&'a A<'a>, &'b B<'b>) -> String,
}

struct A<'a> {
    a_str: &'a str,
}

struct B<'b> {
    b_str: &'b str,
}

fn concat<'a, 'b>(_a: &'a A<'a>, _b: &'b B<'b>) -> String {
    _a.a_str.to_string() + &_b.b_str.to_string()
}

fn length<'a, 'b>(_a: &'a A<'a>, _b: &'b B<'b>) -> usize {
    _a.a_str.len() + _b.b_str.len()
}

fn run1<'a, 'b, 'c>(_a: &'a A<'a>, _b: &'b B<'b>, _f_c: &'c Alg<'a, 'b, 'c>) {
    println!("{}", &(_f_c.alg1)(_a, _b));
}

fn run2<'a, 'b, 'c>(_a: &'a A<'a>, _b: &'b B<'b>, _f_c: &'c Alg<'a, 'b, 'c>) {
    println!("{}", &(_f_c.alg2)(_a, _b));
}

fn main() {
    let f_struct = Alg {
        alg1: &length,
        alg2: &concat,
    };

    for _i in 0..2 {
        let a_str = "ABC";
        let a = A { a_str: a_str };
        for _j in 0..2 {
            let b_str = "BCD";
            let b = B { b_str: b_str };
            println!("{}", concat(&a, &b)); // This works
            println!("{}", (f_struct.alg1)(&a, &b)); // I expect that `concat` or `length` in `f_struct` may finished borrowing `a` or `b' here, as like as `println!("{}",concat(&a,&b))`
                                                     //run1(&a,&b,&f_struct);
                                                     //run2(&a,&b,&f_struct);
        }
    }
}

When I run this, I get an error message like:

error[E0597]: `a` does not live long enough
  --> src/main.rs:43:44
   |
43 |             println!("{}", (f_struct.alg1)(&a, &b)); // I expect that `concat` or `length` in `f_struct` may finished borrowing `a` or `b' here, as like as `println!("{}",concat(&a,&b))`
   |                            --------------- ^^ borrowed value does not live long enough
   |                            |
   |                            borrow used here, in later iteration of loop
...
47 |     }
   |     - `a` dropped here while still borrowed

error[E0597]: `b` does not live long enough
  --> src/main.rs:43:48
   |
43 |             println!("{}", (f_struct.alg1)(&a, &b)); // I expect that `concat` or `length` in `f_struct` may finished borrowing `a` or `b' here, as like as `println!("{}",concat(&a,&b))`
   |                            ---------------     ^^ borrowed value does not live long enough
   |                            |
   |                            borrow used here, in later iteration of loop
...
46 |         }
   |         - `b` dropped here while still borrowed

What is the difference between println!("{}",concat(&a,&b)) and println!("{}",(f_struct.alg1)(&a,&b))?

I thought that I have to indicate something that the function no more borrows the value with lifetime 'a or 'b, but I couldn't find it from rust-by-example or rust-book.

I've tried to apply coercion like 'c: 'a + 'b, but this seems not to help.

These questions are related, but not so clear to me.

Point

  • I want to store functions in the struct
    • I could try another way like not to store functions in the struct
    • But I want to understand the reason when this approach is impossible
hellow
  • 12,430
  • 7
  • 56
  • 79
QuietJoon
  • 490
  • 5
  • 16
  • Unrelated to your actual problem, but you should take a look at [`cargo bench`](https://doc.rust-lang.org/1.12.1/book/benchmark-tests.html) – hellow Jan 10 '19 at 08:27
  • Please use `cargo fmt` (for example at the [playground](https://play.rust-lang.org/)) to format your code according to the rust standards so it is easier to read for everybody. – hellow Jan 10 '19 at 08:32
  • @hellow Thanks for your advices too much. But `rustup` says that `rustfmt` is not able to use for 'x86_64-pc-windows-msvc'.... – QuietJoon Jan 10 '19 at 08:41
  • Have you tried `rustup component add rustfmt`? – hellow Jan 10 '19 at 08:59
  • Yes, it returns the message when I run it on a windows machine. – QuietJoon Jan 10 '19 at 11:00
  • Try `rustup self update`, `rustup update` and then `rustup component add rustfmt` – hellow Jan 10 '19 at 11:34
  • @hellow Sorry for late response. My `rustup` is `rustup 1.16.0 (beab5ac2b 2018-12-06)`, and update it. But I still get a message: `error: toolchain 'stable-x86_64-pc-windows-msvc' does not contain component 'rustfmt' for target 'x86_64-pc-windows-msvc'`. – QuietJoon Jan 14 '19 at 01:22

1 Answers1

3

Quick solution

You have too many lifetime specifiers. Remove the lifetimes for references in your function parameters. Eg. replace alg1: &'c Fn(&'a A<'a>, &'b B<'b>) -> usize with alg1: &'c Fn(&A<'a>, &B<'b>) -> usize (and similar changes to all functions (playground).

Explanation

First, let's simplify your code a bit and rename some of the lifetimes so that we know which one we are talking about:

struct Alg<'Alg_a, 'Alg_b> {
    alg1: &'Alg_b Fn(&'Alg_a A<'Alg_a>) -> usize,
}

struct A<'A_a> {
    a_str: &'A_a str,
}

fn length<'L_a>(a: &'L_a A<'L_a>) -> usize {
    a.a_str.len()
}

fn main() {
    let f_struct = Alg {
        alg1: &length,
    };

    for _i in 0..2 {
        let a_str = "ABC";
        let a = A { a_str: a_str };
        println!("{}", length (&a)); // This works
        println!("{}", (f_struct.alg1) (&a)); // This doesn't
    }
}

You can check on the playground that this exhibits the same error as your code.

When you call (f_struct.alg1)(&a), the compiler tries to find good values for the lifetimes 'Alg_a and 'Alg_b associated with f_struct. Since f_struct is defined outside the loop, then those lifetimes must be the same for all iterations of the loop. However Alg::alg1 is defined as Fn(&'Alg_a …) which means that 'Alg_a must be the lifetime of the parameter a which is only valid for a single loop iteration. Hence the error.

By not specifying the lifetime of the parameter, I allow the compiler to choose different lifetimes for the parameter a and for 'Alg_a, and in particular to choose a different lifetime for the parameter each time the function is called. So the lifetime for the parameter can be limited to a single loop iteration while 'Alg_a may be longer:

struct Alg<'Alg_a, 'Alg_b> {
    alg1: &'Alg_b Fn(&A<'Alg_a>) -> usize,
}

struct A<'A_a> {
    a_str: &'A_a str,
}

fn length<'L_a>(a: &A<'L_a>) -> usize {
    a.a_str.len()
}

fn main() {
    let f_struct = Alg {
        alg1: &length,
    };

    for _i in 0..2 {
        let a_str = "ABC";
        let a = A { a_str: a_str };
        println!("{}", length (&a)); // This works
        println!("{}", (f_struct.alg1) (&a)); // Now this does too
    }
}

playground

Why does calling length directly work?

When calling length directly, the compiler only needs to determine the lifetime 'L_a and there is nothing that requires this lifetime to last for more than a single loop iteration, so there is no conflict.

Note

As noted by @VikramFugro, this only work because a_str = "ABC" creates a slice with a 'static lifetime which can be shrunk down to 'Alg_a or 'L_a as required. Using a dynamic string (let a_str = String::from("ABC")) does not work. We need to declare alg1 as &'Alg_b for<'F_a> Fn(&A<'F_a>) -> usize instead of using the 'Alg_a lifetime on the Alg struct:

struct Alg<'Alg_b> {
    alg1: &'Alg_b for<'F_a> Fn(&A<'F_a>) -> usize,
}

playground

Additionally, Rust 2018 allows us to use an anonymous lifetime '_ instead of the for<'a> … syntax, for example &'Alg_b Fn(&A<'_>) -> usize (playground).

Jmb
  • 18,893
  • 2
  • 28
  • 55
  • 1
    Note that in some cases simply removing the lifetimes in the `Fn` signature is not enough. A trait bound like `Fn(&T) -> &U` implicitly gets translated into `for<'a> Fn(&'a T) -> &'a U` by the compiler, using a _higher rank trait bound_ and the rules of lifetime elision. If the function signature is `Fn(&T, &T) -> &U`, though, lifetime elision is not possible, and you need to specify the HRTB explicitly. See also [the HRTB chapter in the Nomicon](https://doc.rust-lang.org/nomicon/hrtb.html). – Sven Marnach Jan 10 '19 at 10:29
  • Thanks for your answer, I thought that I have to add all the lifetime identifiers. – QuietJoon Jan 10 '19 at 11:28
  • @SvenMarnach note that I only said to remove the lifetimes for the function _parameters._ Return values should keep their explicit lifetimes. – Jmb Jan 10 '19 at 11:38
  • @QuietJoon it would be possible to add all the lifetime identifiers, but then you need to use different lifetimes as required. E.g. `for<'F_a> Fn(&'F_a A<'Alg_a>)` so that the compiler will know that `'F_a` and `'Alg_a` may be different. – Jmb Jan 10 '19 at 11:41
  • Also please note that, `let a_str = "ABC"` has a `'static` lifetime, and `'static` in this can case can be shrunk down to `'Alg_a` (or `'L_a`). For eg. [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=299f223ae42ab4316e48ea7dd77e4b21) would not work. Rust 2018 allows anonymous lifetimes (`'_`), especially where elisions could be confusing. We can use it to our advantage to fix this here. [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=43a4ebe47a1a2656a160b0d605a5082e) – vikram2784 Jan 10 '19 at 12:26
  • @VikramFugro thanks for the comments, edited the answer. – Jmb Jan 10 '19 at 13:25
  • @Jmb It rarely makes sense to keep the lifetime for a return value while removing the explicit lifetime for the arguments. – Sven Marnach Jan 10 '19 at 14:46
  • @SvenMarnach ok, so what I meant is: “_you should remove lifetimes for the function parameters, unless the return value is a reference that should have the same lifetime as one of the parameters or unless more than one parameter should share the same lifetime between them, in which case you should keep the lifetimes for those parameters and only remove the other parameter lifetimes_”. But that's a bit of a mouthful so I concentrated on what's relevant for the question. – Jmb Jan 11 '19 at 07:37