3

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?

wingedsubmariner
  • 13,350
  • 1
  • 27
  • 52

2 Answers2

5

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.

Mihir Luthra
  • 6,059
  • 3
  • 14
  • 39
1

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?

Milan Jaric
  • 5,556
  • 2
  • 26
  • 34
  • 1
    Would expect a little more explaination. – Mihir Luthra Dec 02 '20 at 23:57
  • I hope edit explains better what change means to compiler – Milan Jaric Dec 03 '20 at 00:31
  • This does fix the issue with `in_impl` while still allowing it to be a trait method, however I'm not so sure about your explanation. `wl` cannot be dropped before it is borrowed, the compiler wouldn't allow that. And given your solution, the issue seems to be with the lifetime parameter on `WithLifetime`, not the lifetime of the reference to `wl` itself. – wingedsubmariner Dec 03 '20 at 05:30