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?
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.
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.
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.
Loops. Rust supports four loop expressions: A loop expression denotes an infinite loop. A while expression loops until a predicate is false.
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 i32
s, it yields &i32
s.
Thus, in both the first and second examples, k
is actually binding to &i32
s, not i32
s. 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.
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:
specify a different type to print_me
:
let print_me = |j: &i32| println!("= {}", j);
dereference the value of k
:
print_me(*k);
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:
j
(inferred to be of type i32
, only works because i32
is Copy
)k
((inferred to be of type i32
)j
and k
thus become temporary copies of the i32
inside this element.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With