Why do I have to wrap this HashMap.get in a match?

Hello! New poster here and very excited about learning Rust. I have a python background but ready to dive head first into Rust. I have a hard copy of "the book" and I've read about 3/4 of it thus far. I am working my way through all the exercism exercises and this is related to one of those on the builder pattern.

This is probably yet another &str vs String vs &String question but I haven't found anything yet that helps me understand this.

As context, here is the type definition of the HashMap:

attrs: HashMap<String, String>

I am struggling to understand why the compiler wants me to do this:

pub fn get_attr(&self, key:&str) -> Option<&str> {
                    match self.attrs.get(key) {
                        Some(v) => Some(v),
                        None => None
                    }
                }

Instead of this:

                pub fn get_attr(&self, key:&str) -> Option<&str> {
                    self.attrs.get(key)
                }

The error I get is this:

error[E0308]: mismatched types
  --> src/lib.rs:25:21
   |
25 |                     self.attrs.get(key)
   |                     ^^^^^^^^^^^^^^^^^^^ expected str, found struct `std::string::String`
   |
   = note: expected type `std::option::Option<&str>`
              found type `std::option::Option<&std::string::String>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

I thought that a &String would implicitly (one of the few cases of implicit) coerce into a &str. Also it seems that the match statement above is completely redundant. This is not the first time I've ended up with &String when the compiler wants &str, so I'd like to understand exactly what I am doing wrong.

Any insights greatly appreciated!

Best,
Dave

&String -> &str coercion is based on the Deref trait. Deref coercion can only happen between two references, as seen in the guide to coercions. Since you're producing a totally new value, it won't coerce.

You can get a &str from a &String by using as_str():

self.attrs.get(key).map(String::as_str)
3 Likes

On nightly, you can also write self.attrs.get(key).deref() [1].

5 Likes

Very cool, thanks! It seems to work : ).

Why is this considered producing a new value? I specified the return type to be a reference wrapped in an Option.

Thanks again.

You're creating a new Option, even if it's wrapping a reference.

The basic problem is that references inside another type cannot be coerced.

As an example of why exactly it is not supported, consider what would happen if &[&String] could be coerced into &[&str]. Each element in &[&String] is 8 bytes large, but each element in &[&str] is 16 bytes large. There is no conceivable way to convert between these types.

4 Likes

I see, thanks for the further information to clarify. I'll need to dig in a little more to understand, I was have not conceptualized about the size of the &String rather that the underlying String is on the heap and has an unknown size at compile time.

Thanks again.

deref() not found.

https://play.rust-lang.org/?version=nightly&mode=release&edition=2018&gist=6844a984f0215c1a4ff8c9ff66f209f1

You're right, it has apparently been renamed to as_deref() (you also need to add the inner_deref feature.)

However, unfortunately, it still doesn't seem to work, because it's only implemented for Option<T> and not Option<&T>.

Others have answered this question well, so I'm just going to add my high-level thoughts about differing types for owned values and borrowed values.

Because of ownership (a concept in which I'm including things like lack of a garbage collector), You sometimes need more types for things that you wouldn't need in a higher-level language. So, of the top of my head, you have the pairs String and &str, Vec<T> (or maybe even [T; n]) and &[T], &Path and PathBuf, Box<dyn Trait> and &dyn Trait.

But in rust they are all different types, so the type/borrow checker can track ownership, and can know where things are owned and where they are borrowed. One thing I don't entirely have an intuition for is reference types (&). The important thing is that & != *const (references are not pointers)!! References (&) have magical semantics, and the compiler is allowed to do wierd stuff with them by assuming that they are always valid and that the aliasing rules are always obeyed (single mutable ref xor many immutable refs). These references sometimes have extra data associated with them, like a length for &[T], or a vtable for &dyn Trait. I think a good rule of thumb is "you don't have to understand how they work, just follow the rules, and if you are doing low-level stuff and need to break the rules, use raw pointers, where the optimization tricks that happen with references will not happen."

I don't know if there is some sort of unified theory of how they work, I'd love it if someone posted something like that!