Problem using a HashMap with Option type for value component


#1

Hello! Let me lead off with some lightly annotated code:

use std::collections::HashMap;

pub type ExHash = HashMap<String, Option<String>>;

fn main() {
    let ex = ExHash::new();

    ex.insert("test".to_string(), None);

    // compiles fine
    let a = ex.get("test").unwrap().unwrap();

    // compiler error E0308
    let tval = ex.get("test").unwrap();
    match tval {
        Some(v) => print!("value: {}", v),
        None => print!("no value for key")
    }

    // compiler error E0308
    match ex.get("test") {
        Some(v) => {
            match v {
                Some(sval) => print!("value: {}", sval),
                None => print!("no value for key")
            }
        },
        None => print!("key not found")
    }
}

And this is the error I get when trying to compile the above:

optex.rs:18:9: 18:16 error: mismatched types:
 expected `&core::option::Option<collections::string::String>`,
    found `core::option::Option<_>`
(expected &-ptr,
    found enum `core::option::Option`) [E0308]
optex.rs:18         Some(v) => print!("value: {}", v),
                    ^~~~~~~
optex.rs:18:9: 18:16 help: run `rustc --explain E0308` to see a detailed explanation
optex.rs:19:9: 19:13 error: mismatched types:
 expected `&core::option::Option<collections::string::String>`,
    found `core::option::Option<_>`
(expected &-ptr,
    found enum `core::option::Option`) [E0308]
optex.rs:19         None => print!("no value for key")
                    ^~~~
optex.rs:19:9: 19:13 help: run `rustc --explain E0308` to see a detailed explanation
optex.rs:26:17: 26:27 error: mismatched types:
 expected `&core::option::Option<collections::string::String>`,
    found `core::option::Option<_>`
(expected &-ptr,
    found enum `core::option::Option`) [E0308]
optex.rs:26                 Some(sval) => print!("value: {}", sval),
                            ^~~~~~~~~~
optex.rs:26:17: 26:27 help: run `rustc --explain E0308` to see a detailed explanation
optex.rs:27:17: 27:21 error: mismatched types:
 expected `&core::option::Option<collections::string::String>`,
    found `core::option::Option<_>`
(expected &-ptr,
    found enum `core::option::Option`) [E0308]
optex.rs:27                 None => print!("no value for key")
                            ^~~~
optex.rs:27:17: 27:21 help: run `rustc --explain E0308` to see a detailed explanation
error: aborting due to 4 previous errors

The rustc --explain E0308 seems to indicate that the compiler isn’t inferring the type of the value returned from the HashMap’s get operation.

Is my code correct in form? Should I be accessing these values in some other manner? Are there additional type annotations that I can specify that would make the types more clear to the compiler?

Thanks for any help anyone can provide!


#2

Note that HashMap::get() -> Option<&V>, in your case Option<&Option<String>>, so you’ll need to incorporate that reference in your match patterns. You can reference the internal piece like &Some(ref sval), so it doesn’t try to move the string out.


#3

Thanks for the help! For anyone else that’s curious, this is what I ended up doing:

use std::collections::HashMap;

pub type ExHash = HashMap<String, Option<String>>;

fn main() {
    let mut ex = ExHash::new();

    ex.insert("test".to_string(), None);

    match ex.get("test") {
        Some(ref v) => {
            match &v {
                &&&Some(ref sval) => println!("value: {}", sval),
                &&&None => println!("no value for key")
            }
        },
        None => println!("key not found")
    }
}

I guess the triple ‘&’ for the inner match makes a little bit of sense, but I don’t think I’ve worked through it as much as I need because it still seems like that shouldn’t be needed or that I’m doing something in an obtuse way.


#4

You can also do pattern matching directly on the nested options!

match ex.get("test") {
    Some(&Some(ref sval)) => println!("value: {}", sval),
    Some(&None) => println!("no value for key"),
    None => println!("key not found"),
}

And regarding the triple & in your snippet, v is already a reference, so no need to match &v (that gets rid of one layer of &). Also, no need to Some(ref v) since the inner value is a reference (ref just makes it double reference) – that gets rid of one more layer of &. So now we’re left with just one & and you can even get rid of that by match *v – no more &!. Note that the * here doesn’t mean a move.

There’s also a helpful method as_ref, which converts &Option<T> into Option<&T>.


#5

Thank you for the help, very insightful!

For clarity to anyone else struggling with a similar issue, here’s what krdln describes (as I understand it) when removing the triple &&&:

match ex.get("test") {
    Some(v) => {
        match *v {
            Some(ref sval) => println!("value: {}", sval),
            None => println!("no value for key"),
         }
    },
    None => println!("key not found")
}

One thing I ran into was, at first, trying to remove the “ref” from the Some(ref sval) in addition to mentioned changes, and getting an error about borrowing (which makes sense now).

Using as_ref, I came up with this slightly different version:

match ex.get("test") {
    Some(v) => {
        match v.as_ref() {
            Some(sval) => println!("value: {}", sval),
            None => println!("no value for key"),
        }
    },
    None => println!("key not found")
}

Both fix the immediate issue and taught me a little about paying a little more attention to the compiler output, types and references!

A real win here, for me, is learning a bit more about the pattern matcher, and that it can be leveraged to remove the need for the inner match statement entirely – I find that to be a most elegant solution.

Thanks for everyone’s help!