fold is picky about closures it accepts

Viewed 262

(rust noob here; I'm trying to understand what can/cannot/should/shouldn't be passed by reference in a higher order function situation)

let a = [1, 2, 3];

This invocation works:

let sum = a.iter().fold(0, |acc:  i32, x: &i32| acc + x);

These do not:

let sum = a.iter().fold(0, |acc: i32, x: i32| acc + x);
let sum = a.iter().fold(0, |acc: &i32, x: i32| acc + x);

The error message is

error[E0631]: type mismatch in closure arguments
 --> main.rs:8:22
  |
8 |   let sum = a.iter().fold(0, |acc: &i32, x: i32| acc + x);
  |                      ^^^^    --------------------------- found signature of `for<'r> fn(&'r i32, i32) -> _`
  |                      |
  |                      expected signature of `fn({integer}, &{integer}) -> _`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0631`.

The explanation does not offer anything of interest. It says the arguments of the closure do not match those of the arguments of fold. I however cannot see how it follows from the declaration of fold:

fn fold<B, F>(self, init: B, f: F) -> B
where
    F: FnMut(B, Self::Item) -> B

Why is the second argument supposed to be &{integer}, and the first one {integer}?

2 Answers

The items in the iterator are borrowed from the array, so are &i32 not i32. This form works because the accumulator is owned, while the items are borrowed:

let sum = a.iter().fold(0, |acc: i32, x: &i32| acc + x);

You can convert the iterator so its items are copied instead of referenced, and then the first form will work:

let sum = a.iter().copied().fold(0, |acc: i32, x: i32| acc + x);

The third form can never work. The closure needs to be able to return a new value with which to update the accumulator. The type of the accumulator is i32. It can't be a reference because you can't return a reference from a closure (the original value would be dropped and Rust won't let you return a dangling pointer).

Looking at the declaration of fold()

fn fold<B, F>(self, init: B, f: F) -> B
where
    F: FnMut(B, Self::Item) -> B

you can see that the function has two generic type parameters – the type of the accumulator B and the type of the closure F. The closure type has the trait bound FnMut(B, Self::Item) -> B, meaning that the type of the first argument is identical to the type of the accumulator, while the type of the second argument is the item type of the iterator.

In the invocation

let sum = a.iter().fold(0, |acc, x| acc + x);

we use an iterator with the item type &i32, and your accumulator is initialised to 0, so it's type is inferred to be i32 (which is the default integer type in the absence of any further qualification).

Related