7

I'm coming from a C (and to a lesser extent, C++) background. I wrote the following code snippet:

fn main() {
    let my_array = [1, 2, 3];
    let print_me = |j| println!("= {}", j);
    for k in my_array.iter() {
        print_me(k);
    }
}

This compiled and ran as expected, but then I specified the type of the argument passed to the closure print_me thus:

fn main() {
    let my_array = [1, 2, 3];
    let print_me = |j: i32| println!("= {}", j);
    for k in my_array.iter() {
        print_me(k);
    }
}

I got a compilation error:

error[E0308]: mismatched types
 --> src/main.rs:6:22
  |
6 |             print_me(k);
  |                      ^
  |                      |
  |                      expected i32, found &{integer}
  |                      help: consider dereferencing the borrow: `*k`
  |
  = note: expected type `i32`
             found type `&{integer}`

Now this confused me until I changed k to &k in the for statement, which worked fine:

fn main() {
    let my_array = [1, 2, 3];
    let print_me = |j: i32| println!("= {}", j);
    for &k in my_array.iter() {
        print_me(k);
    }
}

It seems that I misunderstood the for syntax itself -- or maybe the exact workings of an iterator -- or maybe the usage syntax of a reference vis-a-vis a pointer [which are related but distinct in C++].

In the construct for A in B { C1; C2; ... Cn }, what exactly are A and B supposed to be?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Avijit
  • 183
  • 4

2 Answers2

13

First of all, here's a link to the definition of for in the reference.

To summarise, B is any expression which evaluates to something that can be converted into a value that implements the Iterator<T> trait, whilst A is a irrefutable pattern that binds values of type T.

In your specific case, slice::iter returns an Iter<i32>, which implements Iterator<Item = &i32>. That is, it doesn't yield i32s, it yields &i32s.

Thus, in both the first and second examples, k is actually binding to &i32s, not i32s. When you specified the type of the closure, you were actually specifying the wrong type. The reason the final example works is because A is a pattern, not a variable name. What &k is actually doing is "de-structuring" the &i32, binding the i32 part to a variable named k.

The "irrefutable" part simply means that the pattern must always work. For example, you can't do for Some(x) in thingy where thingy implements Iterator<Option<_>>; Some(x) would not necessarily be valid for every element in the iterator; thus, it's a refutable pattern.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
DK.
  • 55,277
  • 5
  • 189
  • 162
6

Many iterators actually return a reference rather than a value. To be sure, you have to check the return type of .iter(), which should be of the form Iterator<Item = X>: X will be the type of the variable returned.

So here:

fn main() {
    let my_array = [1, 2, 3];
    let print_me = |j: i32| println!("= {}", j);
    for k in my_array.iter() {
        print_me(k);
    }
}

This X is &i32 (a reference to i32), and therefore k has type &i32.

This is why, when calling print_me, there is an error: &i32 is passed where i32 is expected.


There are multiple possible fixes here:

  1. specify a different type to print_me:

    let print_me = |j: &i32| println!("= {}", j);
    
  2. dereference the value of k:

    print_me(*k);
    
  3. change the type of k by destructuring in the loop:

    for &k in my_array.iter() { ... }
    

The destructuring occurs because for .. in accepts an irrefutable pattern, so you can pattern match like you would do in a match expression, except that the variable's type has to match (otherwise you get a compiler time error).

To better illustrate it, we can use a slightly more complicated example:

fn main() {
    let my_array = [(1, 2), (2, 3), (3, 4)];
    let print_me = |a: i32, b: i32| println!("= {} {}", a, b);
    for &(j, k) in my_array.iter() {
        print_me(j, k)
    }
}

The type of my_array is [(i32, i32)]: an array of tuples of 2 i32. The result of .iter() is therefore of type Iterator<Item = &(i32, i32)>: an iterator to a reference to a tuple of 2 i32 aka &(i32, i32).

When we use the irrefutable pattern &(j, k) what happens is that we destructure the tuple so that:

  • the first element binds to j (inferred to be of type i32, only works because i32 is Copy)
  • the second element binds to k ((inferred to be of type i32)

j and k thus become temporary copies of the i32 inside this element.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722