通过引用传递参数的闭包解决类型不匹配问题

4

当我试图编译下面的Rust代码时,遇到了一对奇怪的错误。在寻找类似问题的其他人时,我发现另一个具有相同组合(看似相反)错误的问题, 但无法将该解决方案推广到我的问题。

基本上,我似乎在Rust的所有权系统中缺少微妙之处。在尝试编译此处(非常精简的)代码时:

struct Point {
    x: f32,
    y: f32,
}

fn fold<S, T, F>(item: &[S], accum: T, f: F) -> T
where
    F: Fn(T, &S) -> T,
{
    f(accum, &item[0])
}

fn test<'a>(points: &'a [Point]) -> (&'a Point, f32) {
    let md = |(q, max_d): (&Point, f32), p: &'a Point| -> (&Point, f32) {
        let d = p.x + p.y; // Standing in for a function call
        if d > max_d {
            (p, d)
        } else {
            (q, max_d)
        }
    };

    fold(&points, (&Point { x: 0., y: 0. }, 0.), md)
}

我收到了以下错误信息:
error[E0631]: type mismatch in closure arguments
  --> src/main.rs:23:5
   |
14 |     let md = |(q, max_d): (&Point, f32), p: &'a Point| -> (&Point, f32) {
   |              ---------------------------------------------------------- found signature of `for<'r> fn((&'r Point, f32), &'a Point) -> _`
...
23 |     fold(&points, (&Point { x: 0., y: 0. }, 0.), md)
   |     ^^^^ expected signature of `for<'r> fn((&Point, f32), &'r Point) -> _`
   |
   = note: required by `fold`

error[E0271]: type mismatch resolving `for<'r> <[closure@src/main.rs:14:14: 21:6] as std::ops::FnOnce<((&Point, f32), &'r Point)>>::Output == (&Point, f32)`
  --> src/main.rs:23:5
   |
23 |     fold(&points, (&Point { x: 0., y: 0. }, 0.), md)
   |     ^^^^ expected bound lifetime parameter, found concrete lifetime
   |
   = note: required by `fold`

(这是一个Rust Playground链接,方便查看代码。)

我认为我提供给fold函数的函数应该能够正确地进行类型检查... 我错过了什么,我该如何解决?


两个注意事项:99.99%的情况下,请使用&[T]代替&Vec<T>。此外,当一个空向量被传递给您的test函数时会发生什么?那个引用会在哪里存在? - Shepmaster
@Shepmaster,我实际上正在使用 Vec<T> 来代替我正在开发的自定义函数式列表类型,只是为了保持问题简单并关注我得到的错误。此外,在我的代码的非精简版本中,一个空列表会导致 panic! 并显示没有任何事可做。基本上,我尝试将代码简化到仍然可以获得错误消息的地步,同时省略任何多余的东西。 - Trevor
没问题,减少问题总是好的!然而,当前的MCVE试图返回对局部变量的引用,这将导致生命周期错误,可能阻止我给你答案,因为我无法使其编译。而且你永远不应该让手指输入&Vec<T>,所以它甚至不应该出现在示例中 ^_^。 - Shepmaster
出于好奇,为什么 &[T] 比 &Vec<T> 更受欢迎? - Trevor
1个回答

1
简单来说,如果闭包内联编写或存储为变量,推断出的生命周期是不同的。将闭包内联编写并删除所有多余的类型:
fn test(points: &[Point]) -> (&Point, f32) {
    let init = points.first().expect("No initial");
    fold(&points, (init, 0.), |(q, max_d), p| {
        let d = 12.;
        if d > max_d {
            (p, d)
        } else {
            (q, max_d)
        }
    })
}

如果你真的需要out-of-band的闭包,可以查看如何为闭包参数声明生命周期?
此外,我必须从输入数组中取出first值,因为你不能返回对局部变量的引用。方法上不需要生命周期参数;它们将被推断。
要使代码实际编译,需要提供有关fold方法的更多信息。具体来说,你必须指示传递给闭包的引用与传入的参数具有相同的生命周期。否则,它可能只是对局部变量的引用:
fn fold<'a, S, T, F>(item: &'a [S], accum: T, f: F) -> T
where
    F: Fn(T, &'a S) -> T,
{
    f(accum, &item[0])
}

与Rust有关的问题是#41078

那么内联传递的闭包将从它们被传递到的函数推断出类型和生命周期,但存储为变量的闭包会在没有该上下文的情况下尝试确定类型和生命周期? - Trevor
2
@Trevor,我在回答中留下了一些模糊的地方,因为我不确定。通过一些手势,我可能会说出“早期”或“晚期”的限制,但我不确定这个术语的准确性,更不用说与真正原因的关系了。好消息是,像那样内联闭包被认为是惯用的。 - Shepmaster
好的!非常感谢你的帮助;指出我的局部变量(以及它短暂的生命周期)帮助我解决了接下来的问题,而这个问题在我最初询问的错误中根本看不到。 - Trevor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接