【问题标题】:Why does this simple closure fail while the other two functions succeed?为什么这个简单的闭包失败了,而其他两个函数成功了?
【发布时间】:2019-11-08 21:27:20
【问题描述】:

我构建了一个无法开始工作的闭包示例,也找不到任何不应该工作的原因。为什么最后一次闭包编译失败?

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);
}

输出:

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


【解决方案1】:

从关注Type mismatches resolving a closure that takes arguments by referenceHow to declare a lifetime for a closure argument? 看来,解决方案是改变:

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

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

虽然我不知道为什么。当闭包被内联指定时,它似乎与推断的生命周期有关,而当它存储在变量中并稍后使用时。但尚不清楚为什么&amp;S 需要'a 生命周期,而&amp;S 不是返回的结果。如果您理解这一点,请在评论中解释。

虽然这个问题已“解决”,但最初发布的精简失败案例实际上并没有帮助我解决真正的问题,因为我无法编辑代码的源代码我遇到了https://docs.rs/walkdir/2.2.9/walkdir/struct.IntoIter.html#method.filter_entry 问题

当我尝试将存储的回调传递给 filter_entry 方法时,问题就出现了。解决方案是在filter_entry 签名中加入显式生命周期,如本文前面所述,但只有在您想编辑第三方代码时才能这样做。我认为不幸的是,该特定问题的答案是“您不能将存储的闭包与filter_entry 一起使用”

【讨论】:

  • 我认为您不会得到比 other answer 中提供的更好的解释,即 Rust 编译器(当前)根本不够聪明,无法在闭包时执行适当的推理在一个变量中。
  • this discussion 的eddyb 的帮助下,我已经能够更多地了解这个问题。基本要点是我无法理解为什么编译器会关心&amp;'a S 的生命周期,因为它没有用作输出,也没有被闭包捕获。答案基本上是@user4815162342 所说的,即编译器不够聪明,无法知道闭包没有在某处存储&amp;S,因此它需要保证引用将比闭包更有效。
  • “如何声明...”答案中的constrain 函数实际上也适用于此!只需小心地将constrain 应用于闭包literal,而不是稍后在filter 调用站点(这是我的第一次尝试)。请参阅我的答案以获取可运行的示例。
【解决方案2】:

我认为不幸的是,该特定问题的答案是“您不能将存储的闭包与 filter_entry 一起使用”

Shepmaster's answer 的方法可以用来实现这个目标。就像在那个答案中一样,我们可以定义一个通用的constrain 函数,该函数需要一个具有满足filter 所需的生命周期边界的类型。我们将函数应用到闭包并然后将其存储到。请注意,在调用 filter(这是我的第一次尝试)时调用 constrain(cb) 不起作用,因为编译器在传递给 constrain 时无法推断闭包变量的类型,而不是在它被传递时传递给filter

constrain 的调用在运行时没有任何影响,它只是引导编译器推断出我们需要的 filter 变量的生命周期边界。这允许在不修改其签名的情况下存储闭包并调用filter

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.

【讨论】:

    猜你喜欢
    • 2016-09-23
    • 2012-01-24
    • 1970-01-01
    • 2018-09-12
    • 2018-06-23
    • 2014-05-31
    • 1970-01-01
    • 2011-08-08
    • 1970-01-01
    相关资源
    最近更新 更多