Why does this simple closure fail while the other

2020-04-16 05:02发布

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,
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

标签: rust closures
2条回答
爷的心禁止访问
2楼-- · 2020-04-16 05:39

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.

查看更多
beautiful°
3楼-- · 2020-04-16 05:54

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"

查看更多
登录 后发表回答