2014-02-11

This is a non-exhaustive non-academic, example-driven, rather-practical guide on how to use the HashMap in Rust. As a side effect of working on the HashMap some hands-on examples of Rust's unique concepts of ownership and borrowing references will be demonstrated.

Prelude

Before we get to the real stuff, here's is something which is not directly HashMap, but more Rust pointer and friends stuff, however got me stuck for some time.

fn add_map(map: &mut HashMap<~str, uint>, k: &str, v: uint) {
  map.insert(k.to_owned(), v); 
}

In order to be able to modify, make sure the map is mutable both in the add_map method as in the declaration of the HashMap. If you don't you will get an error saying error: cannot borrow immutable dereference of & pointer as mutable.

let mut map: HashMap<~str, uint> = HashMap::new();
let key = ~"key string";
let val = 1;

add_map(&map, key, val);
println!("{:?}", key);

Notice that by using &map, we pass a reference to our map to the function add_map, in rust terms we borrow the map, but we remain the owner of it.

The key gets passed to add_map as a reference, too, hence you need to_owned() in order to copy it into the key field of the map. The map now owns its own copy of key.

If you don't need to access the key after adding to the map, you could move it into the map. This will be more efficient, no memory copying, but trying to access key afterwards will result in a error: use of moved value: key:

fn add_map_move(map: &mut HashMap<~str, uint>, k: ~str, v: uint) {
  map.insert(k, v); 
}

let mut map: HashMap<~str, uint> = HashMap::new();
let key = ~"key string";
let val = 1;

add_map_move(&map, key, val);
println!("{:?}", key);          // -> error: use of moved value: `key`

Save yourself some sorrow and do not do this (unless you know exactly what you're doing)

// DON'T!
let mut map: HashMap<&str, uint> = HashMap::new();

Creating and inserting

Boring...

let mut map = HashMap::<~str, uint>::new();
map.insert(~"foo", 1);
// or

let mut map: HashMap<~str, uint> = HashMap::new();
map.insert(~"bar", 2);

An immutable HashMap

You can create a HashMap as mutable and then freeze it (by assigning without mut), to prevent further modification:

fn create_immutable() -> HashMap<~str, uint> {
  let mut map = HashMap::<~str, uint>::new();
  map.insert(~"foo", 1);
  map // insert returns whether the key was already present in the map, hence returns the map
}

let imap = create_immutable(); // without the mut it is immutable
imap.insert(~"foo", 1); // -> error: cannot borrow immutable local variable as mutable

Another option to create an immutable HashMap would be to use HashMap::from_iterator

Inserting and modifying at the same time

For the definitions of those functions see std::hashmap::HashMap

Most straightforward insert

let mut map = HashMap::<~str, uint>::new();
map.insert(~"foo", 1);

doing a find, and an insert if the searched key does not exist

let mut map = HashMap::<~str, uint>::new();
assert!(!map.contains_key(&~"foo"));

map.find_or_insert(~"foo", 1);
assert!(map.contains_key(&~"foo"));

Doing a find, inserting with a proc(), using the key to construct the value

let mut map = HashMap::<~str, ~str>::new();
assert!(!map.contains_key(&~"foo"));

map.find_or_insert_with(~"foo", |k| *k + ~"bar");
assert_eq!(*map.get(&~"foo"), ~"foobar");

Love this one - insert a new one if the current isn't present, update if it is present

let mut map = HashMap::<~str, uint>::new();
assert!(!map.contains_key(&~"foo"));

// running this for the first time, will add "foo" with the value 1
map.insert_or_update_with(~"foo", 1, |_k, v| *v += 1);
assert_eq!(*map.get(&~"foo"), 1);

// running the same for the second time, will add +1 to "foo"
map.insert_or_update_with(~"foo", 1, |_k, v| *v += 1);
assert_eq!(*map.get(&~"foo"), 2);

and last but not least the almighty mangle. I had a couple of serious struggles with mangle.

let mut map = HashMap::<~str, uint>::new();
assert!(!map.contains_key(&~"foo"));

// for key "foo", take the initial value 1 and add +10
map.mangle(~"foo", 1,         // take this value as a
           |_k, a| a + 10,    // apply this function on a
           |_k, v, a| *v -= 2
          ); 
assert_eq!(*map.get(&~"foo"), 11);

let x = *map.mangle(~"foo", 1,
           |_k, a| a + 10,
           |_k, v, _a| *v -=  2 // take the current value in ~"foo" and apply the function on it
          );

assert_eq!(*map.get(&~"foo"), 9);

The first mangle does not assign the return value, it just inserts into the map under the key ~"foo" the value of a + 10, where a is the seconed parameter of the function.

The second mangle fires the second proc passed, which subtracts 2 from the value stored under ~"foo". Notable here is, that the return value of the mangle function is dereferenced with *, thus a copy of the value is created. The mangle returns a reference into the map, to the value stored under ~"foo" and therefor borrows the whole map! Any further operations on map wouldn't be possible.

Getting it back

The basic get gets the value, but fails if the value isn't present. To get back a mutable version use get_mut instead.

fn test_get(){
  let mut map = HashMap::<~str, uint>::new();
  map.insert(~"foo", 1);

  let r = *map.get(&~"foo");

  map.insert(~"bar", 2); // works!
}

As in the previous example with mangle, make sure you dereference the returned value to make a copy if you want to be able to access the map afterwards.

Instead of the dereference, you can make your intent more expclicit and use get_copy

let mut map = HashMap::<~str, uint>::new();
map.insert(~"foo", 1);

let r = map.get_copy(&~"foo");
assert_eq!(r, 1);

map.insert(~"bar", 2); // works!

Fun stuff about get_mut is that once you got the reference to the value, you can modify the value inside the map directly!

let mut map = HashMap::<~str, uint>::new();
map.insert(~"foo", 1);

{
  let mut r = map.get_mut(&~"foo");
  *r += 1;
}
assert_eq!(*map.get(&~"foo"), 2);    // Look Ma, I updated the map!

The curlies around the mutation operation allow us to introduce a new lifetime, and at the end of it we return the borrow for r (and the map along with it), so we can again access the map to get the value one more time.

Failsafe getting

Now find which returns None if the key could not be found and Some if it was found. Again, substitute for find_mut to get back a mutable version. This should be the preferred way of retrieving data from a hash.

let mut map = HashMap::<~str, uint>::new();
map.insert(~"foo", 1);

let s = match map.find(&~"foo") {
  None      => 0,
  Some(v)   => *v
};

As in get_copy you can use find_copy instead of find

let mut map = HashMap::<~str, uint>::new();
map.insert(~"foo", 1);

let r = match map.find_copy(&~"foo") {
  None      => 0,
  Some(v)   => v    // no derference needed here
};

assert_eq!(r, 1);

Pop

This comes in handy sometimes - getting a value from the hash and at the same time removing it from the hash

let mut map = HashMap::<~str, uint>::new();
map.insert(~"foo", 1);

let r = match map.pop(&~"foo") {
  None      => 0,
  Some(v)   => v
};

assert_eq!(r, 1);
assert!(!map.contains_key(&~"foo")); // ~"foo" is gone

Iterating

You can also walk over the key - value pairs in a hash

let mut map = HashMap::<~str, uint>::new();
map.insert(~"foo", 1);
map.insert(~"bar", 2);

let mut numbers = ~[];
for (_k,v) in map.iter() {
  numbers.push(*v);         // copying the values
}

assert!(numbers.contains(&2));
assert!(numbers.contains(&1));

//assert_eq!(numbers, ~[2, 1]); // beware, order might change, so this would fail randomly

To simultaneously iterate over the hash and remove the values at the same time, use iter_move.

As an aside, a more functional way - instead of pushing to the numbers vector, to get the values would be

let v = map.values().map(|v| v.clone()).to_owned_vec();

Preserving Order

Be cautious, you cannot rely on the order in which the pairs are coming to your iterator! If you need a specific order use a collections::TreeMap instead:

extern mod extra;
use collections::TreeMap;

#[test]
fn test_treemap() {
  let mut map = TreeMap::<~str, uint>::new();
  map.insert(~"foo", 1);
  map.insert(~"boo", 2);

  let mut numbers = ~[];
  for (_k,v) in map.iter() {
    numbers.push(v.clone());
  }
  assert_eq!(numbers, ~[2, 1]);
}

Version

The examples run on rustc head

% rustc -v
rustc 0.10-pre (47e1445 2014-02-09 20:41:27 -0800)

For more discussion head over to Reddit



blog comments powered by Disqus