Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the exact definition of the for loop in Rust?

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?

like image 726
Avijit Avatar asked Dec 01 '14 09:12

Avijit


People also ask

HOW DO FOR loops work in Rust?

A for loop's expression in Rust is an iterator that ​returns a series of values. Each element is one iteration of the loop. This value is then bound to variable and can be used inside the loop code to perform operations.

How does a for loop work?

A "For" Loop is used to repeat a specific block of code a known number of times. For example, if we want to check the grade of every student in the class, we loop from 1 to that number. When the number of times is not known before hand, we use a "While" loop.

What are the different types of loops in Rust and what are they used for?

A loop is a programming structure that repeats a sequence of instructions until a specific condition is satisfied. Similar to other programming languages, Rust also has two types of loops: Indefinite Loop: While loop and Loop. Definite Loops: For loop.

Does Rust have loop syntax?

Loops. Rust supports four loop expressions: A loop expression denotes an infinite loop. A while expression loops until a predicate is false.


2 Answers

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.

like image 122
DK. Avatar answered Sep 17 '22 19:09

DK.


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.

like image 42
Matthieu M. Avatar answered Sep 20 '22 19:09

Matthieu M.