How to handle the value I get from a HashMap

Viewed 3825

I want my getv() function return the value from a HashMap. I get errors no matter how I modify my code:

enum Type {
    TyInt,
    TyBool,
}

struct TypeEnv {
    Env: HashMap<char, Type>,
}
impl TypeEnv {
    fn set(&mut self, name: char, ty: Type) {
        self.Env.insert(name, ty);
    }

    fn getv(self, name: char) -> Type {
        match self.Env.get(&name) {
            Some(Type) => Type, // <------- Always error here
            None => Type::TyInt,
        }
    }
}
2 Answers

HashMap::get returns an Option<&Type>, not an Option<Type>, which is why just returning the matched value fails to compile.

get provides a reference because in Rust you cannot simply return the actual value that is in the hash table - you need to either clone it (make a copy, potentially expensive), remove it from the container and transfer it to the caller, or return a reference to the value inside. HashMap::get chooses the last option because it is cheap while providing the greatest flexibility to the caller, which can choose to either inspect the value or clone it.

For your enum that consists of a single machine word, copying the value would be the optimal approach. (For more complex enums, a better approach is to return a reference as shown in Simon's answer.) Copying requires:

  • making Type copyable, by marking it with the Copy trait using the #[derive(Copy, Clone)] directive at the type definition.

  • returning the copy from getv by dereferencing the matched value using *matched_value, or by using & in the pattern.

Finally, your getv method consumes the object, which means that you can call it only once. This is almost certainly not what was intended - it should accept &self instead. With those changes, the resulting code looks like this:

use std::collections::HashMap;

#[derive(Copy, Clone)]
enum Type {
    TyInt,
    TyBool,
}

struct TypeEnv {
    env: HashMap<char, Type>,
}

impl TypeEnv {
    fn set(&mut self, name: char, ty: Type) {
        self.env.insert(name, ty);
    }

    fn getv(&self, name: char) -> Type {
        match self.env.get(&name) {
            Some(&v) => v,
            None => Type::TyInt,
        }
    }
}

If the type needs to contain non-Copy data, then you can make it only Clone instead, and return v.clone().

You don't have to make your type Copy or Clone ... if you're happy working with the reference.

When things like this pop up while writing Rust, its generally best to re-evaluate how you're calling the code. Can it be made simpler?

You can either have the caller expect the Option<&V>, or just force something out... perhaps like this:

fn getv(&self, name: char) -> &Type {
    self.env.get(&name).unwrap_or(&Type::TyInt)
}

This simplifies your code and makes it pretty clear what you're after. Here it is in action:

use std::collections::HashMap;

#[derive(Debug)]
enum Type {
    TyInt,
    TyBool,
}

struct TypeEnv {
    env: HashMap<char, Type>,
}

impl TypeEnv {
    fn set(&mut self, name: char, ty: Type) {
        self.env.insert(name, ty);
    }

    fn getv(&self, name: char) -> &Type {
        self.env.get(&name).unwrap_or(&Type::TyInt)
    }
}

fn main() {
    let mut te = TypeEnv {
        env: HashMap::new(),
    };
    {
        let arg = te.getv('c');

        println!("{:?}", arg); // "TyInt" - because nothing was found
    }

    te.set('c', Type::TyBool);

    let arg = te.getv('c'); // "TyBool" - because we stored it in the line above.

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

Also, your existing implementation of getv takes ownership of self ... notice how I added the reference getv(&self). If you don't have this, this type (as far as I can tell) becomes basically useless.

Related