Why does `iter()` and `into_iter()` does the same thing if the object is a reference?

If I have a value, into_iter() collects values (and iter collects references):

let mut x = HashMap::new();
x.insert("1", 1);
x.insert("2", 2);
x.insert("3", 3);
let y: Vec<(&str, usize)> = x.into_iter().collect();

Whereas if we have a reference, we don't collect values, but references:

fn foo(x: &HashMap<&str, usize>) {
    // This doesn't works
    // let y: Vec<(&str, usize)> = x.into_iter().collect();

    // This works
    let y: Vec<(&&str, &usize)> = x.into_iter().collect();
}

Why? What surprised me even more is that all the types I am using are Copy (I was expecting that into_iter would either move the items from the container if the container was a value, or copy them if the container was a reference).

HashMap has fn iter(&self) method,and &HashMap has fn into_iter(self)method.

The latter exist so we can write code below:

for (key_ref, val_ref) in &self.map {
    ...
}
2 Likes

But iter() already does that.

for (key_ref, val_ref) in &self.map.iter() {
    ...
}

To make it possible to use a &HashMap directly as the thing to iterate over in a for loop, it must implement the IntoIterator trait, and this trait requires it (the reference type) to have an into_iter method. The iter method is just a convenience method defined on HashMap to disambiguate between into_iter on HashMap and the one on &HashMap.

3 Likes

Ok. Is there a way to collect value from a &HashMap then?

What do you mean with "collect value"? You can't take ownership through an immutable reference, if that's what you're asking. You can clone it, though.

1 Like

As always, it depends. You want to consume the map and collect its value? You can't since you only have a shared reference to the map. You want to drain the map and collect drained values? You can't drain the map with its shared reference. You want to iterate over references of the values and collect their copies? Sure you can, just map_ref.values().cloned().collect().

https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html#method.values

https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.cloned

https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.collect

I want to have the same vec, that what I could get with this:

let map: HashMap<Foo, Bar> = /* ... */;
let vec: Vec<(Foo, Bar)> = map.into_iter().collect();

Obviously, if I have a &HashMap, I cannot consume its value, but if both Foo and Bar are copy, I should be able to to iterate over the map, copy each (key, value) pair, and then collect it in a Vec. This operation shouldn't need to allocate a new HashMap, just to be able to consume it.

What I want is semantically equivalent to this, but without the allocation of the unwarted_copy HashMap:

let ref_map: &HashMap<Foo, Bar> = /* ... */;

// this shouldn't be done since it will do work that isn't needed (dynamic allocation,
// 1 extra copy of everything, …)
let unwanted_copy = ref_map.clone();

// This is going to iterate and copy all pairs `(key, value)` from the map into a new vec.
// This works is needed and wanted.
let vec: Vec<(Foo, Bar)> = unwanted_copy.into_iter().collect();

// at that point, `vec` contains (in unspecified order) all `(key, value)` of `ref_map`
// and `ref_map` wasn't modified

If key and value are copy, you can do this:

let map: HashMap<Foo, Bar> = /* ... */;
let vec: Vec<(Foo, Bar)> = map.iter().map(|(k,v)| (*k, *v)).collect();

With a .clone() instead of a dereference if it is only Clone.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.