1

An algorithm I wrote builds a temporary HashMap. Once it's finished, I'm only interested in the values of the hashmap, so I'd like to transfer ownership of the values from the HashMap<K, V> to a Vec<V>.

With a simplified example hashmap:

fn main() {
    use std::collections::HashMap;
    let mut h: HashMap<_, _> = HashMap::new();
    h.insert(1, "foo".to_owned());
}

I can do:

  • let vals: Vec<&String> = h.values().collect(); - which is short and sweet, but the hashmap still owns the values;
  • let vals: Vec<String> = h.values().cloned().collect() (as in this question) - the result is what I need, but I was taught to avoid the extra clones;
  • let vals: Vec<String> = h.into_iter().map(|(_k, v)| v).collect(); - does what I need without a clone, but is a bit ugly.

The actual values are a mid-sized struct ({String, Vec<String>}, under a KB total).

Should I default to avoiding clone in this case or is it premature optimization? Is there an idiomatic way to do this that I'm missing?

Stargateur
  • 24,473
  • 8
  • 65
  • 91
Nickolay
  • 31,095
  • 13
  • 107
  • 185

2 Answers2

3

.into_iter().map(|(_, v)| v) is the idiomatic way to do it. That not ugly at all.

If you want you could do:

use std::collections::hash_map;
use std::collections::HashMap;
use std::iter::{ExactSizeIterator, FusedIterator};

struct IntoValues<K, V> {
    iter: hash_map::IntoIter<K, V>,
}

impl<K, V> IntoValues<K, V> {
    fn new(map: HashMap<K, V>) -> Self {
        Self {
            iter: map.into_iter(),
        }
    }
}

impl<K, V> Iterator for IntoValues<K, V> {
    type Item = V;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|(_, v)| v)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.iter.size_hint()
    }
}
impl<K, V> ExactSizeIterator for IntoValues<K, V> {}

impl<K, V> FusedIterator for IntoValues<K, V> {}

trait HashMapTool {
    type IntoValues;
    type Item;
    fn into_values(self) -> Self::IntoValues;
}

impl<K, V> HashMapTool for HashMap<K, V> {
    type Item = V;
    type IntoValues = IntoValues<K, V>;
    fn into_values(self) -> Self::IntoValues {
        IntoValues::new(self)
    }
}

fn main() {
    let mut h: HashMap<_, _> = HashMap::new();
    h.insert(1, "foo".to_owned());

    let _vals: Vec<_> = h.into_values().collect();
}
Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • 1
    Thanks! Nice to see how I'd go about implementing `into_values()`, but I think I'll use the into_iter().map() to avoid all the extra code. – Nickolay Aug 10 '19 at 03:18
1

Note that as of Rust 1.54.0, the into_values method has been implemented for HashMap and BTreeMap in the standard library.

fn main() {
    let mut h: HashMap<i32, String> = HashMap::new();
    h.insert(1, "foo".to_owned());
    let _vals: Vec<String> = h.into_values().collect();
}

https://github.com/rust-lang/rust/blob/master/RELEASES.md#stabilized-apis-6

remykarem
  • 2,251
  • 22
  • 28
  • 1
    Thanks for bringing it up here! Looking at https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.into_values , the implementation is essentially what @Stargateur suggested bundled into the standard library. – Nickolay Aug 30 '22 at 20:37
  • How would you do this if the hashmap is owned by a struct? – Evan Carroll Mar 18 '23 at 04:39
  • @EvanCarroll it depends if you want to (1) 'move' the map out of the struct and not use the struct anymore, or (2) reuse the map in the struct. For (1), you could simply do `my_struct.map.into_values()`. For (2), you could do `my_struct.map.clone().into_values()`. – remykarem May 06 '23 at 17:22