HashMap k: Borrow<Q> get(&Q) some error

Below is my code example, that code segment passed complier error check.
But, this is an error, can someone help me with this confusing things.

use std::borrow::Borrow;
use std::collections::HashMap;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AbType {
    Rss,
    Gold,
}

impl Borrow<str> for AbType {
    fn borrow(&self) -> &str {
        match self {
            &AbType::Gold => "gold",
            &AbType::Rss => "rss",
        }
    }
}

fn main() {
    let mut hash = HashMap::new();
    hash.insert(AbType::Gold, 10);

    let val = hash.get("gold");
    let goldVal = hash.get(&AbType::Gold);
    println!("val: {:?}, goldVal: {:?}", val, goldVal);
}

the output is as blow:

val: None, goldVal: Some(10)

So, there one thing confusing me, why the val & goldVal not equal?

You need to manually implement Hash for AbType to satisfy the following requirement from the Borrow docs:

In particular Eq , Ord and Hash must be equivalent for borrowed and owned values: x.borrow() == y.borrow() should give the same result as x == y .

3 Likes

Thanks for your reply.

I might not make clear of my question, Sorry. The question is , why this code segment passed the complier check, but I still get a wrong answer, so, Does there any error with my code ?

That is exactly what he was answering. The automatically derived Hash impl doesn't produce the same results as hashing the string representation, hence, you must implement it manually.

1 Like

The explanation is here:

Generic code typically uses Borrow<T> when it relies on the identical behavior of these additional trait implementations. These traits will likely appear as additional trait bounds.

In particular Eq, Ord and Hash must be equivalent for borrowed and owned values: […]

And also here:

The key may be any borrowed form of the map’s key type, but Hash and Eq on the borrowed form must match those for the key type.

Regarding your question:

Yes, there is an error in your code!

The error is that you implemented Borrow but didn't ensure that Hash behaves the same for the owned (AbType) and the borrowed (str) type. By writing #[derive(Hash)], you instructed the compiler to automatically provide a Hash implementation, but this isn't the same as str's Hash implementation (for the particular str values you provide).

If you implement Borrow, you must ensure that Hash::hash acts the same irrgegardless of whether you feed a AbType::Rss or a string "rss" into it.

The compiler is not capable to catch these kind of errors. They are called "logic errors".

This is how you can fix the code:

 use std::borrow::Borrow;
 use std::collections::HashMap;
+use std::hash::{Hash, Hasher};
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum AbType {
     Rss,
     Gold,
 }
 
 impl Borrow<str> for AbType {
     fn borrow(&self) -> &str {
         match self {
             &AbType::Gold => "gold",
             &AbType::Rss => "rss",
         }
     }
 }
 
+impl Hash for AbType {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let s: &str = self.borrow();
+        s.hash(state);
+    }
+}

(Playground)

Hope that helps.

4 Likes

Thanks your reply

Very Thanks for your details reply, (Thanks you very much),

also thanks @H2CO3 @2e71828 H2CO3 @

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.