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,v
can’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:a
can be used in multiplefor x in a { ... }
loops becausea
has type[u8; 3]
, and theu8
type implementsCopy
, and soa
is 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
Vec
has all the methods that Rust’sslice
has, because Vec implementsDeref
for slice, and Rust has implicit deref coercion. That is, any Vec can be used as a slice.The
iter
method thatVec
uses has signature:pub fn iter(&self) -> Iter<'_, T>
- Rust automatically converts the
v
inv.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).