QuietJoon
QuietJoon

Reputation: 491

Function refered from the stored in struct does not give the ownership up

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

Upvotes: 1

Views: 130

Answers (1)

Jmb
Jmb

Reputation: 23463

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

Upvotes: 3

Related Questions