References And Iteration in Rust
References and Iteration #
There’s a slight possibility that what I’ve written here may not be 100% correct. In such case I’ll come back and update my stance as I learn more about the language.
As a Rust noob, yesterday I came to know about the “pattern” of iteration. It’s like the combination of having two things: rust passing you the references of values and how to use them, and how the values are owned/copied/moved in all of this. There was a good enough discussion that I had on #rust-beginners where the community was really helpful.
So iteration is done using for
keyword, and while some data types provide
iteration out of the box, I was mostly looking at iterating BTreeMap or HashMap,
let’s call them containers.
Iterating a container also depends on whether it contains a value which
implements Copy
or not. Copy
is a trait, which if implemented by a type
allows the type to move by copying its value instead of copying the references
using clone()
. Most of the primitives like i32
, f32
, etc implement
it. Other types like &str
, String
, Vec<T>
do not, and that’s why we cannot
move their borrowed copies. A more detailed discussion is available at StackOverflow
To do this iteration, rust provides a pattern:
let hashmap: HashMap<T1, T2> = HashMap::new();
// fill the hash with values
for (k, v) in hashmap.iter() {
// use k and v
}
both k
and v
are actually references to the keys and values of the
hashmap, and are used as such, so these have the types &T1
and &T2
respectively.
In case I write the following:
let hashmap: HashMap<T1, T2> = HashMap::new();
for (&k, &v) in hashmap.iter() {
}
the k
and v
inside the for
expression become the values,
instead of becoming the references. Rust pattern matches and removes the
reference operator before passing the values inside the for
. The values
now have the types T1
and T2
respectively.
This matters in case k
or v
implement Copy
or not. In case they do,
then nothing bad happens (like i32). In case they don’t (eg. Vecv
in the first snippet is actually &Vec<T>
, or
&HashMap<T1, T2>
inside the for
expression, and Vec<T>
in the second
one. So usage depends on what we are doing.
For example, here’s an iteration of HashMap<i32, char>
:
fn main() {
let mut copy_types: HashMap<i32, char> = HashMap::new();
copy_types.insert(12, 'a');
copy_types.insert(13, 'b');
iterate_copy_types_with_ref(©_types);
iterate_copy_types_without_ref(©_types);
}
fn iterate_copy_types_with_ref(h: &HashMap<i32, char>) {
// Taking a reference of non-copy types works
for (&k, &v) in h.iter() {
println!("{}, {}", k, v);
}
}
fn iterate_copy_types_without_ref(h: &HashMap<i32, char>) {
// Not taking a reference of non-copy types works as well
for (k, v) in h.iter() {
println!("{}, {}", k, v);
}
}
Using &
inside the iterator doesn’t matter because both i32
and char
are Copy
and the compiler copies these.
In case the type does not implement Copy
like a Vec<T>
, then
it becomes important how we use the references with iterators.
fn main() {
let mut ncopy_types: HashMap<i32, Vec<&str>> = HashMap::new();
// Vec<T> is a non copy type.
ncopy_types.insert(12, vec!["My", "name", "is"]);
ncopy_types.insert(13, vec!["your", "name", "is"]);
iterate_non_copy_types(ncopy_types);
}
fn iterate_non_copy_types(h: HashMap<i32, Vec<&str>>) {
// v is actually a `&Vec<&str>` here.
for (&k, v) in h.iter() {
println!("{}, {:?}", k, v);
// 13, ["your", "name", "is"]
// 12, ["My", "name", "is"]
}
for (&k, v) in h.iter() {
// Again, since c implements Copy, there's no need actually to
// use &c -- we can use `c` and it will work fine.
for &c in v {
println!("{}", c);
}
}
}