14
votes

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.

1

1 Answers

15
votes

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: