rustborrow-checker

Indexing a `HashMap<&String, V>` with `&str`


What is the proper way to index into a HashMap<&String, V> with a &str? Rust reports that &String is not Borrow<str>, which is required for indexing. (This seems a bit silly to me; if T: Borrow<U> then surely &T: Borrow<U> should also hold?)

use std::collections::HashMap;

fn main() {
    let strings = vec!["x".to_owned(), "y".to_owned(), "z".to_owned()];
    let m = [0, 1, 2]
        .into_iter()
        .map(|i| (&strings[i], i))
        .collect::<HashMap<&String, usize>>();
    let zero = m["x"];
}
error[E0277]: the trait bound `&String: Borrow<str>` is not satisfied
 --> src/main.rs:9:18
  |
9 |     let zero = m["x"];
  |                  ^^^ the trait `Borrow<str>` is not implemented for `&String`, which is required by `HashMap<&String, usize>: Index<&_>`
  |
  = help: the trait `Borrow<str>` is implemented for `String`
  = help: for that trait implementation, expected `String`, found `&String`
  = note: required for `HashMap<&String, usize>` to implement `Index<&str>`

Obviously, because I constructed this particular HashMap, I could go back and change its key type to &str instead. But supposing I'm handed a HashMap<&String, V>, what is the proper way to index it? Constructing a whole String just to take a reference to it seems wasteful.


Solution

  • With #![feature(hash_raw_entry)], it is possible to access keys directly by hash (with a followup check for equality), which lets one get around borrowing limitations.

    #![feature(hash_raw_entry)]
    
    use std::hash::{Hash, Hasher, BuildHasher};
    use std::collections::HashMap;
    
    fn main() {
        let strings = vec!["x".to_owned(), "y".to_owned(), "z".to_owned()];
        let m = [0, 1, 2]
            .into_iter()
            .map(|i| (&strings[i], i))
            .collect::<HashMap<&String, usize>>();
        
        let key = "x";
    
        let mut hasher = m.hasher().build_hasher();
        key.hash(&mut hasher);
        let hash = hasher.finish();
        
        let zero = m.raw_entry().from_hash(hash, |found_key| found_key == &key);
        println!("{zero:?}");
        // Some(("x", 0))
    }