Borrowing in closures


#1

Hello all. I’m trying to understand borrowing inside closures. I wrote the following small program to illustrate:

fn main() {
    let nums = (1..11).map(|i| i * 2).filter(|&i| i > 5).collect::<Vec<_>>();
    println!("nums = {:?}", nums);

    let nums2 = (0..100).map(|i| i * i).filter(|i| i % 3 == 0).collect::<Vec<_>>();
    println!("nums2 = {:?}", nums2);
}

The code compiles. My question is why do I need to take in a reference &i in the first closure passed to filter, but not in the 2nd call to filter? In the 2nd call, I could also use &i and the code still compiles, but it’s required in the 1st call. Thanks.


#2

In the first you’re not taking a reference – filter is giving you a reference, and |&i| is pattern-matching through it, so you’re effectively dereferencing it. The type of i ends up just being an integer. This only works because it’s a Copy type, otherwise you’d get an error about moving the value out of a reference.

In |i| the type of i will be left as the reference filter gave you. But Rem is implemented for ref%value for integers, so i % 3 is fine. (I’m not sure why comparisons don’t allow that mismatch.)


#3

It’s not that you aren’t taking a reference in the second case. The closure gets a reference in both cases, but in the first closure you’re dereferencing it when binding to i.

That you can leave the dereferencing out in the second case is because the % operator i.e. the Rem trait has implementations for references as both arguments (i.e. you can do any of 1 % 1, &1 % 1, 1 % &1 or &1 % &1).

The PartialOrd trait used by comparison operators like > does not allow this, although I’m not quite sure why an implementation couldn’t be written.


#4

My guess would be that it is common to compare addresses and to avoid mistakenly converting the type, it was left without an implementation.

As for %, if it didn’t convert, it would mean pointer arithmetic which should be avoided in safe code, that’s my guess why it automatically makes the conversion(dereferencing) here…


#5

There’s no pointer arithmetic in any case; comparing two references will compare the objects, not addresses (if you want to compare addresses, you have to cast to *const T first).

The question is just why there is no impl<T> PartialOrd<&T> for T.


#6

Ok, then it doesn’t make sense :slight_smile:


#7

Thanks @cuviper and @birkenfeld for the detailed answers. I didn’t even know about the Rem trait, and I’m starting to see that traits are central to how everything is implemented in Rust. Now I have a clue on where (and how) to look whenever I run into similar issues, instead of the old “keep trying stuff until the compiler accepts it” approach I was using.