2

I've constructed a closure example that I can't get to work, nor can I find any reason why it shouldn't work. Why does it fail to compile on the last closure?

Playground

struct S {}

fn filter<P>(predicate: P)
where
    P: Fn(&S) -> bool,
{
    predicate(&S {});
}

fn main() {
    // this works
    filter(|_s| true);

    // this also works
    fn cb1(_s: &S) -> bool {
        true
    }
    filter(cb1);

    // but this doesn't work
    let cb2 = |_s| true;
    filter(cb2);
}

Output:

error[E0631]: type mismatch in closure arguments
  --> /tmp/closure.rs:19:5
   |
18 |     let cb2 = |_s| true;
   |               --------- found signature of `fn(_) -> _`
19 |     filter(cb2);
   |     ^^^^^^ expected signature of `for<'r> fn(&'r S) -> _`
   |
note: required by `filter`
  --> /tmp/closure.rs:3:1
   |
3  | fn filter<P>(predicate: P) where P: Fn(&S) -> bool,
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0271]: type mismatch resolving `for<'r> <[closure@/tmp/closure.rs:18:15: 18:24] as std::ops::FnOnce<(&'r S,)>>::Output == bool`
  --> /tmp/closure.rs:19:5
   |
19 |     filter(cb2);
   |     ^^^^^^ expected bound lifetime parameter, found concrete lifetime
   |
note: required by `filter`
  --> /tmp/closure.rs:3:1
   |
3  | fn filter<P>(predicate: P) where P: Fn(&S) -> bool,
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Andrew Moffat
  • 405
  • 3
  • 11
  • It looks like your question might be answered by the answers of [Type mismatches resolving a closure that takes arguments by reference](https://stackoverflow.com/q/36415348/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Nov 08 '19 at 21:32

2 Answers2

2

From following Type mismatches resolving a closure that takes arguments by reference and How to declare a lifetime for a closure argument? it appears the solution is to change:

fn filter<P>(predicate: P)
where
    P: Fn(&S) -> bool,
{
    predicate(&S {});
}

to

fn filter<'a, P>(predicate: P)
where
    P: Fn(&'a S) -> bool,
{
    predicate(&S {});
}

Though I'm not sure why. It seems to be related to inferred lifetimes when a closure is specified inline vs when it is stored in a variable and used later. But it's unclear why &S needs an 'a lifetime, when &S is not a result that is returned. If you understand this, please explain in a comment.

Though this question is "solved", the trimmed-down failure case posted originally does not actually help my true problem, because I cannot edit the source of the code I am having trouble with https://docs.rs/walkdir/2.2.9/walkdir/struct.IntoIter.html#method.filter_entry

The issue manifested when I tried to pass a stored callback into the filter_entry method. The solution would be to put in explicit lifetimes in the filter_entry signature, as described earlier in this post, but you can only do that if you want to edit the third party code. I think unfortunately the answer for that particular problem is "you can't use a stored closure with filter_entry"

Andrew Moffat
  • 405
  • 3
  • 11
  • I don't think you will get a better explanation than one offered in the [other answer](https://stackoverflow.com/a/46198877/1600898), which is that Rust compiler (currently) simply isn't smart enough to perform the appropriate inference when the closure is in a variable. – user4815162342 Nov 09 '19 at 14:41
  • I have been able to understand more about the problem, with the help of eddyb in [this discussion](https://github.com/rust-lang/rust/issues/41078#issuecomment-552061906). The basic gist was that I couldn't understand why the compiler would care about the lifetime of `&'a S`, when it wasn't used as an output, nor was anything captured by the closure. The answer is basically what @user4815162342 said, which is that the compiler isn't smart enough to know that the closure isn't storing `&S` somewhere, and so it needs a guarantee that the reference will outlive the closure. – Andrew Moffat Nov 09 '19 at 17:07
  • The `constrain` function from the "How to declare..." answer is actually applicable here as well! One just has to be careful to apply `constrain` to the closure _literal_, and not later at the `filter` invocation site (as was my first attempt). See my answer for a runnable example. – user4815162342 Nov 09 '19 at 18:32
1

I think unfortunately the answer for that particular problem is "you can't use a stored closure with filter_entry"

The approach from Shepmaster's answer can be applied to achieve this goal. Like in that answer, we can define a generic constrain function requiring a type with the kind of lifetime bounds that will be required to satisfy filter. We apply the function to the closure and then store it to. Note that calling constrain(cb) when invoking filter (which was my first attempt) doesn't work because the compiler cannot infer the type of the closure variable when it is passed to constrain any more than it could when it was being passed to filter.

The invocation of constrain has no effect at run-time, it just guides the compiler to infer the lifetime bounds for the variable that we need for filter. This allows storing the closure and calling filter without modifying its signature:

fn constrain<F>(fun: F) -> F
where
    F: for<'a> Fn(&'a S) -> bool,
{
    fun
}

fn main() {
    let cb = constrain(|_s| true);
    filter(cb);
}

Playground.

user4815162342
  • 141,790
  • 18
  • 296
  • 355