A little confused about String vs str, ownership, and Copy trait

Greetings,

I am attempting to map a vector of hashmaps to a vector. I'm encountering a situation that I need some advice on.

use std::collections::HashMap;

fn main() {
    let mut foo: Vec<HashMap<String, String>> = Vec::new();

    for i in 1..=10 {
        foo.push(
            HashMap::from([
                ("Number".to_owned(), i.to_string()),
            ])  
        );  
    }   

    println!("{:?}", foo);

    let bar: Vec<String> = foo.into_iter().map(move |x| x["Number"]).collect();

    println!("{:?}", bar);
}

gives:

error[E0507]: cannot move out of index of HashMap<String, String>
--> src/main.rs:16:57
|
16 | let bar: Vec = foo.into_iter().map(move |x| x["Number"]).collect();
| ^^^^^^^^^^^ move occurs because value has type String, which does not implement the Copy trait

I can solve the problem by appending '.to_owned()' to x["Number"], but am wondering if I am setting up the code correctly by using String types for my HashMap.

Does anything look wrong, or unorthodox, in the above code? Is there a clean way to perform the map without "cloning" the string (via .to_owned())?

Thanks for any advice and help.

I believe you'll have to clone the string if you don't want to remove it from the map. (String does not implement Copy because an allocation is required, so you have to do a clone).

To remove it from the map without cloning (meaning it is simply moved) I did this:

    let bar: Vec<String> = foo
        .iter_mut()
        .map(|x| x.remove("Number").unwrap())
        .collect();

I'm not sure if that is the best way, but it is one way.

1 Like

Since you consume foo with into_iter(), I assume you don't need to preserve the values (including the HashMaps) any more. So, you basically want to convert (map) each HashMap into one of its entries:

    let bar: Vec<String> = foo
        .into_iter()
        .map(|mut x| x.remove("Number").unwrap())
        .collect();

This will panic if it encounters a HashMap without a "Number" entry (as your original code would have). You could also, instead, just ignore such HashMaps:

    let bar: Vec<String> = foo
        .into_iter()
        .flat_map(|mut x| x.remove("Number"))
        .collect();
2 Likes

Thank you @quinedot . I appreciate the extra info about @jumpnbrownweasel reply.

Since the original vector should always have the "Number" key, a panic would be appropriate to signal a serious error in the system. So, for my use-case, I think I'll try out the into_mut with remove as opposed to the flat_map with remove.

Thanks again for the help!

Thank you @jumpnbrownweasel . This looks good and I'll read up on the iter_mut, too. Thanks for taking time to help me. Cheers!

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.