Type mismatches resolving a closure that takes arg

2019-01-20 14:53发布

问题:

I'm encountering a strange pair of errors while trying to compile my Rust code below. In searching for others with similar problems, I came across another question with the same combination of (seemingly opposing) errors, but couldn't generalize the solution from there to my problem.

Basically, I seem to be missing a subtlety in Rust's ownership system. In trying to compile the (very pared down) code here:

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

I get the following error messages:

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`

(A Rust Playground link for this code, for convenience.)

It seems to me that the function I'm supplying to fold should type-check properly... what am I missing here and how can I go about fixing it?

回答1:

The short version is that there's a difference between the lifetimes that are inferred if the closure is written inline or stored as a variable. Write the closure inline and remove all the extraneous types:

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

Additionally, I had to pull the first value from the input array — you can't return a reference to a local variable. There's no need for lifetime parameters on the method; they will be inferred.

To actually get the code to compile, you need to provide more information about the fold method. Specifically, you have to indicate that the reference passed to the closure has the same lifetime as the argument passed in. Otherwise, it could just be a reference to a local variable:

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

The related Rust issue is #41078.