Rust has Objects too: A brief introduction to hash maps

Objects, as they are implemented in JavaScript, are arguably one of the most powerful and robust tools available to a JavaScript developer. Object literals, easy object extensibility, JSON, and the fact that almost everything in JavaScript is an object are all awesome concepts that web developers mostly take for granted. Any developer who primarily develops for the web will immediately miss the ergonomics of JavaScript objects when testing the waters in a new language. Trust me, I have definitely not been exempt from this in my own foray into the strange and wonderful world of Rust. This article will (hopefully) be a brief introduction to the std::collection::HashMap collection in Rust.

Disclaimer: I’m by no means a Rust expert — like a few of you out there, I’m merely a professional web developing mortal. My only goal is to not misguide any potential readers too badly during the process of me figuring these things out for myself. You’ve been warned.

What exactly is a hash map?

If you have a pretty good idea of what a hash map is, you can skip on down to the next section.

In general terms, a hash map is a type of data structure that allows you to create an association between a key (Generally a character or string) and a value (potentially anything). Imagine a spreadsheet or table with the following structure:

Table 1

This table creates an association between a name (in hash map terms our key) and a phone number (the value). This is the basic structure of the "map" part of "hash map". The "hash" part has to do with how the association can be managed in a way that allows for efficient operations like look-ups and modifications to the key-value pairings. Each key (like individual 'Names' in the example above) is passed through whats called a "hashing function" in order to get a unique number that is explicitly tied to the original key string. The value can then be stored in an array, at the index that was computed by the hashing function. The table below illustrates this new structure:

Table 2

The implementation of hash maps varies greatly between different programming languages, but generally the advantage of a hash map is that it provides very fast look-ups and very fast insertions. However, there are lots of nuances to this data structure, and components of hash maps like the hashing function can be implemented in many different ways. There are also other specifics, like how to handle cases where the hashing function returns the same index for multiple key strings (aka "collisions") that won't be covered in this post.

Before we dive in to the specifics of HashMap in Rust, I think it's only responsible to list just a few of the other names that may refer to the general concept of hash maps in the context of other programming languages (hopefully some of these make sense if they didn't before):

  • Hash
  • Map
  • Table
  • Hash Table
  • Dictionary
  • Symbol Table
  • Associative Array

Keep in mind that there are nuances to these concepts, and they’re not all technically referring to the same thing, but that’s about as much detailed computer science as you’re going to get out of a web developer.

HashMap in Rust

HashMap in Rust is part of the standard library, and is an all around useful tool to have at your disposal. Although I hinted at Rust's hash maps being the equivalent to objects in JavaScript, the reality is that JavaScript objects are nearly ubiquitous. While HashMap is certainly a powerful tool, it's not used in the same fundamental way in Rust that objects are in JavaScript. This speaks to the huge difference in complexity between the two languages, and the relative ignorance of anyone who tries to draw trivial comparisons between the two.

Anyway.

Like Vec, (which you can read all about in my very, very short overview) HashMap is part of what Rust calls "collections". Keys in Rust's hash maps can be many different types, including strings, integers, and interestingly, booleans. Like vectors, hash maps are dynamically sized and store their values on the heap at runtime.

Making your very own HashMap

Creating a HashMap can be done quite easily with the wonderfully convenient HashMap::new(), or using HashMap::with_capacity(uint) if you prefer a predetermined size for your hash maps. Another option is to start with a vector of tuples (like so: [('key', 0)]) and using the handy collect method. If you happened to have two vectors with keys and values just lying around, you could even use a combination of zip (which creates a vector of tuples from two initial vectors) and collect, as illustrated below:

let keys = vec![String::from("key"), String::from("key2")]; let vals = vec![1, 2]; let hash_map: HashMap<_, _> = keys.iter().zip(vals.iter()).collect();

Take note of the underscores in HashMap<_, _>: basically this notation lets us use Rust's robust type inference to decide what the types of our key and value will be without us explicitly saying so.

Reading and writing

HashMaps also conveniently allow us to insert and read values based on a key. It isn't quite as simple as JavaScript, but nevertheless it can be done using the get() and insert() methods respectively. Inserting into a hash map will overwrite the previous value, if one existed.

Further reading

This post barely qualifies as an overview for Rust’s HashMap, but as always if you're interested in learning more about them from people much more qualified from me, check out some of the resources below:

Thanks for reading.


Originally published at tndl.io on November 29, 2018.