19

I want to insert into a HashMap but keep an immutable borrow of the key to pass around to places. In my case the keys are strings.

This is one way:

use std::collections::HashMap;
let mut map = HashMap::new();
let id = "data".to_string();  // This needs to be a String
let cloned = id.clone();

map.insert(id, 5);

let one = map.get(&cloned);
let two = map.get("data");
println!("{:?}", (one, two));

But this requires a clone.

This one worked until Rust 1.2.0:

use std::collections::HashMap;
use std::rc::Rc;
use std::string::as_string;

let mut map = HashMap::new();
let data = Rc::new("data".to_string()); // This needs to be a String
let copy = data.clone();
map.insert(data, 5);

let one = map.get(&copy);
let two = map.get(&*as_string("data"));
println!("{:?}", (one, two));

How can I accomplish this with Rust 1.2.0?

Ideally I would want to put a key into a HashMap but keep a reference to it, and allow me to access elements in it with &str types, with no extra allocating.

Michael Eden
  • 948
  • 1
  • 7
  • 18

1 Answers1

17

The short answer is that you can't. When you insert something into the HashMap, you transfer ownership. Doing so invalidates any references that you might have to the key, as the key is moved into the memory allocated by the map.

RFC 1194 (Set Recovery) proposes a way to get references to the key stored by a HashSet (not map). Further information and research was needed to justify supporting this for HashMap as well. However, this still wouldn't help you as you would need to know the key (or something that can be used to look it up) in order to look it up again. However, you've already put the key in the collection at that point.

Your second solution works because you aren't actually giving ownership of the String to the map, you are giving it ownership of a type that models shared ownership via reference counting. The clone call simply increments the reference count, and this models how a lot of dynamic languages would solve this problem.

use std::collections::HashMap;
use std::rc::Rc;

fn main() {
    let mut map = HashMap::new();
    let data = Rc::new("data".to_string());
    map.insert(data.clone(), 5);

    let v = map.get(&data);
    println!("{:?}", v);
}

Some unstable features might help you. The most efficient one is HashMap::raw_entry_mut:

#![feature(hash_raw_entry)]

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    let id = "data";

    let (k, _v) = map
        .raw_entry_mut()
        .from_key(id)
        .or_insert_with(|| (String::from(id), 0));

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

A shorter but slightly less efficient solution uses Entry::insert

#![feature(entry_insert)]

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    let id = "data";

    let entry = map.entry(String::from(id)).insert(0);
    let k = entry.key();

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

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • This solution is great! Although I cannot access data in my map with `&str` types anymore, which is a must. :/ – Michael Eden Sep 04 '15 at 17:35
  • This RFC looks like what I need, too bad that HashMaps weren't considered for it. – Michael Eden Sep 04 '15 at 17:44
  • 3
    Would it be technically difficult or impossible to add a `fn insert_and_get(&mut self, value: T) -> &T` that returns a reference to the newly inserted datum? – Boiethios Apr 19 '19 at 14:33
  • @FrenchBoiethios I do not think that it would be hard or impossible, no. I've also not tried it myself :-) – Shepmaster Apr 19 '19 at 14:40
  • FYI, someone can propose an implementation: https://github.com/rust-lang/rust/pull/60142#issuecomment-487416553 – Boiethios Aug 01 '19 at 10:13
  • HashSets have an experimental method for `get_or_insert()` that returns a reference to what you inserted: https://doc.rust-lang.org/std/collections/struct.HashSet.html#method.get_or_insert – TinyTimZamboni Jun 16 '20 at 03:23
  • `Entry::insert` (which never stabilized) has been removed and replaced with [`Entry::insert_entry`](https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html#method.insert_entry) (which is also not stabilized yet) – kbolino Nov 18 '22 at 20:11