Get real value or None from a HashMap

Viewed 109

I have a struct that I would like to fill based on a potentially incomplete hash map:

#[derive(Debug)]
struct Sue {
    children: Option<u8>,
    cats: Option<u8>,
    samoyeds: Option<u8>,
    pomeranians: Option<u8>,
    akitas: Option<u8>,
    vizslas: Option<u8>,
    goldfish: Option<u8>,
    trees: Option<u8>,
    cars: Option<u8>,
    perfumes: Option<u8>,
}

impl Sue {
    fn from_line(line: &str) -> Sue {
        let data: Vec<&str> = line.split(" ").collect();

        let items: HashMap<String, u8> = data[2..data.len()]
            .chunks(2)
            .map(|item| {
                (
                    String::from(item[0].trim_end_matches(":")),
                    String::from(item[1].trim_end_matches(",")).parse().unwrap(),
                )
            })
            .collect();

        println!("{:?}\n", items);

        Sue {
            // children: match items.get("children"){
                // Some(val) => Some(*val),
                // None => None,
            // },
            children: *items.get("children"),
            // cats: Some(0),
            // samoyeds: Some(0),
            // pomeranians: Some(0),
            // akitas: Some(0),
            // vizslas: Some(0),
            // goldfish: Some(0),
            // trees: Some(0),
            // cars: Some(0),
            // perfumes: Some(0),
        }
    }
}

In the snippet above, the items hash map may or may not contain a key for each field of the Sue struct. My idea was to build a hash map with the input, and then for each field of the struct, try to access the corresponding key of the hashmap. If the key is in the hashmap, return the dereferenced value. If the key isn't in the hashmap, return None.

I know I can do that with a match (there is a couple of lines commented showing how I would do this), but I was wondering if there is a more efficient way to do this (it's a lot of boilerplate; one match for each field).

If I run the code above, I'll get:

error[E0614]: type `Option<&u8>` cannot be dereferenced

Any idea? I do want to keep None if the key isn't found. Also for reference, this snippet is for advent of code 2015 day 16.

2 Answers

Use copied or cloned to convert an Option<&T> to Option<T>. The first is available if T: Copy, the second if T: Clone.

u8 implements both so you can use either method.

children: items.get("children").copied(),

For u8, either of the methods John Kugelman mentioned work great. However, there are some types that are neither Copy nor cheaply cloneable (maybe they don't implement Clone at all, or they do but it is costly).

A third option is to remove the item from the map:


struct ExpensiveClone(u8);

impl Clone for ExpensiveClone {
    fn clone(&self) -> Self {
        std::thread::sleep(Duration::from_secs(1));
        Self(self.0)
    }
}

In this case, cloning would likely be prohibitively expensive. Instead, if you have owned or mutable access to the map, you can remove it, transferring ownership of the value from the map to your function, without needing to clone the data:

let mut map = HashMap::new();
map.insert(1, ExpensiveClone(1));
let expensive_clone = map.remove(&1).unwrap();
assert_eq!(expensive_clone.0, 1);

It goes without saying that this only works if you can modify the map though

Related