Example of How Rust Can be Confusing, Iteration and Refs
Tags: programming.rust
I recently tripped up over this when writing Rust:
Consider this example, which features a few different ways of iterating over arrays/vectors in Rust:
fn main() {
let a: [u8; 3] = [3, 7, 5];
let v: Vec<u8> = vec![3, 7, 5];
for &x in &a {
println!("{}", x);
}
for &x in a.iter() {
println!("{}", x);
}
for x in a {
println!("{}", x);
}
for &x in &v {
println!("{}", x);
}
for &x in v.iter() {
println!("{}", x);
}
for x in v {
println!("{}", x);
}
}That code works.
With the array a, it iterates over &a, a.iter(), and a,
as well as with the vector v over &v, v.iter(), and v.
Amending the example a bit, the following does not compile:
// Note: does NOT compile
fn main() {
let a: [u8; 3] = [3, 7, 5];
for &x in &a {
println!("{}", x);
}
for &x in a.iter() {
println!("{}", x);
}
for x in a {
println!("{}", x);
}
for x in a { // added this `for`
println!("{}", x);
}
let v: Vec<u8> = vec![3, 7, 5];
for &x in &v {
println!("{}", x);
}
for &x in &v.iter() { // changed to &v.iter()
println!("{}", x);
}
for x in v {
println!("{}", x);
}
for x in v { // added this `for`
println!("{}", x);
}
}What doesn’t work:
- The expression
&v.iter()is incorrect.
- i.e.
v.iter()is implicitly the same as(&v).iter(), whereas&v.iter()is the same as&(v.iter()).- I find it intuitive that
&(v.iter())cannot be an iterator, since it’s an immutable reference.
- I find it intuitive that
- The
for x in v { ... }consumes the value ofv; so,vcan’t be used after this.
- Perhaps what’s a bit confusing is that the
for x in a { ... }can be written out twice. – But, here the code’s a bit sneaky:acan be used in multiplefor x in a { ... }loops becauseahas type[u8; 3], and theu8type implementsCopy, and soais copied when writing outfor x in a { ... }twice.
Some notes for reference regarding the first point:
The Rust docs page for Vec does have the iter method. It’s under “Methods from Deref<Target = [T]>”. This is the same as the slice primitive’s iter method.
Rust’s
Vechas all the methods that Rust’sslicehas, because Vec implementsDereffor slice, and Rust has implicit deref coercion. That is, any Vec can be used as a slice.The
itermethod thatVecuses has signature:pub fn iter(&self) -> Iter<'_, T>- Rust automatically converts the
vinv.iter()to(&v).iter(). This is called ‘auto-referencing’.- https://doc.rust-lang.org/book/ch05-03-method-syntax.html
- https://doc.rust-lang.org/reference/expressions/method-call-expr.html#r-expr.method.candidate-receivers-refs
- Rust automatically converts the
There’s no single thing about the above which I’d consider too confusing.
The documentation/references above are all for things which pretty much “just work”. And one mistaken assumption in the above code is about operator precedence.
I wanted to write about this because it’s a subtle case where incorrect expressions looked very similar to correct expressions.
It’s an example of something that I found surprising when writing Rust, and it’s not something that I’d have to think about if I were writing Python or TypeScript. (Or, the other way: it’s not something I’d have to think about when writing C).