简体   繁体   中英

Issue with lifetime parameters and function pointers

Consider this code:

struct WithLifetime<'a> {
    s: &'a str
}

impl WithLifetime<'_> {
    fn in_impl(&self) -> bool {
        self.s == "a"
    }
}

fn out_of_impl(wl: &WithLifetime<'_>) -> bool {
    wl.s == "a"
}

fn higher_order(f: fn(&WithLifetime<'_>) -> bool) -> bool {
    let s = "a";
    let wl = WithLifetime { s };
    f(&wl)
}

fn main() {
    higher_order(out_of_impl); // This line compiles
    higher_order(WithLifetime::in_impl); // This line does not
}

The final line of main fails to compile with this error:

error[E0308]: mismatched types
  --> src/main.rs:23:18
   |
23 |     higher_order(WithLifetime::in_impl); // This line does not
   |                  ^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected fn pointer `for<'r, 's> fn(&'r WithLifetime<'s>) -> _`
              found fn pointer `for<'r> fn(&'r WithLifetime<'_>) -> _`

As far as I can figure out, in_impl and out_of_impl should be exactly the same function. They both take a reference to a WithLifetime , and don't care about the lifetime of that reference or the lifetime parameter of the WithLifetime instance. Both should be valid parameters to pass to higher_order . The compiler does not agree.

What is the problem with passing in_impl to higher_order , and why doesn't the compiler allow this? What can I do to pass struct methods on structs with lifetime parameters to higher-order functions?

First, let's find out the types for all the 3 functions:

fn main() {
    let x: i32 = out_of_impl;
    let x: i32 = WithLifetime::in_impl;
    let x: i32 = higher_order;
}

Playground

Type of out_of_impl :

for<'r, 's> fn(&'r WithLifetime<'s>) -> bool {out_of_impl}

Type of WithLifetime::in_impl :

for<'r> fn(&'r WithLifetime<'_>) -> bool {WithLifetime::<'_>::in_impl}

Type of higher_order :

fn(for<'r, 's> fn(&'r WithLifetime<'s>) -> bool) -> bool {higher_order}

higher_order accepts a function in which both the lifetimes aren't named until the function is called. So it basically accepts a function which works for any lifetimes 'r and 's . out_of_impl satisfies that criteria.

But in case of WithLifetime::in_impl , the inner lifetime needs to be known beforehand for<'r> fn(&'r WithLifetime<'_>) .

To pass WithLifetime::in_impl , you would need to change it to:

fn in_impl<'b, 'c>(abc: &'b WithLifetime<'c>) -> bool {
    abc.s == "a"
}

Playground

Now, this function works for any arbitrary lifetimes 'b and 'c .


To accept in_impl without changing it, as @ Milan did,

fn higher_order<'b>(f: fn(&WithLifetime<'b>) -> bool) -> bool {
    let s = "a";
    let wl = WithLifetime { s };
    f(&wl)
}

Playground

Now, higher_order has a type:

for<'b> fn(for<'r> fn(&'r WithLifetime<'b>) -> bool) -> bool {higher_order}

It accepts a function where lifetime 'r is defined only when the function is called and liftime 'b is known beforehand.

This works for out_of_impl because it accepts any arbitrary lifetimes. Also works for in_impl because now the signature matches higher_order .

How does for<> syntax differ from a regular lifetime bound? has a pretty nice explaination for HRTB.

In higher_order function, wl is dropped before it is borrowed, instead, change that function to this

fn higher_order<'b>(f: fn(&WithLifetime<'b>) -> bool) -> bool {
    let s = "a";
    let wl= WithLifetime { s };
    f(&wl)
}

EDIT:

In simple words, fn higher_order<'b>(f: fn(&WithLifetime<'b>) -> bool) -> bool this 'b means that higher_order body lifetime will match fn lifetime.

Otherwise, if fn don't care what lifetime higher_order has, and this implicitly means wl value too since it is the body of high_order function, then compiler can drop wl at the same line it is declared since point is to save memory, right?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM