4

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Trevor
  • 138
  • 10
  • Two notes: use `&[T]` instead of `&Vec` 99.99% of the time. Also, what happens to your `test` function when an empty vector is passed to it? Where would that reference live? – Shepmaster Apr 05 '16 at 01:34
  • @Shepmaster So I'm actually using `Vec` to stand in for a custom functional-style list type that I'm working on, just to keep the question simple and focus on the errors I'm getting. Also, in the non pared-down version of my code, an empty list `panic!`s and says there's nothing to do. Basically, I tried to cut down on the code to the point where I'd still get my error messages while leaving out anything extraneous. – Trevor Apr 05 '16 at 01:44
  • That's fine, and it's great to reduce problems! However, the current MCVE attempts to return a reference to a local variable which will cause lifetime errors, *possibly* preventing me from giving you an answer because I can't get it to compile. And you should never let your fingers type `&Vec`, so it shouldn't even occur in an example ^_^. – Shepmaster Apr 05 '16 at 01:47
  • Out of curiosity, why is &[T] preferred over &Vec? – Trevor Apr 05 '16 at 02:14

1 Answers1

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

If you truly must have the closure out-of-band, review How to declare a lifetime for a closure argument?.

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • So closures that are passed inline have their types and lifetimes inferred from the function they're being passed to, but closures stored as variables try to determine types and lifetimes without that context? – Trevor Apr 05 '16 at 02:30
  • 2
    @Trevor I left it a bit vague in the answer because I'm not sure. With a bit of hand waving, I might say things like "early" or "late" bounds, but I'm not sure how accurate that terminology would be, much less the relation to the real reason. The good news is that inlining the closure like that is considered idiomatic. – Shepmaster Apr 05 '16 at 02:35
  • Fair enough! Thanks for all your help; pointing out my local variable (and it's associated short lifetime) helped me solve my next issue, which I couldn't even see because of the errors I originally asked about. – Trevor Apr 05 '16 at 02:57