Zig hashmaps explained

If you just got started with Zig, you might quickly want to use a hashmap. Zig provides good defaults, with a lot of customization options.

Here I will try to guide you into choosing the right hashmap type.

60-second explainer

You probably want:

var my_hash_map = std.StringHashMap(V).init(allocator);

Or if you do not have string keys, you can use an Auto hashmap instead:

var my_hash_map = std.AutoHashMap(K, V).init(allocator);

Where K and V are your key and value data types, respectively. e.g. []const u8 for a string.

You can then use these APIs:

Insert a value

try my_hash_map.put(key, value);

Insert a value, assert entry does not already exist

try my_hash_map.putNoClobber(key, value);

Note putNoClobber may be renamed to something like putAssumeNoEntry in the near future: ziglang/zig#10736

Get a value

var value = my_hash_map.get(key);
if (value) |v| {
    // got value "v"
} else {
    // doesn't exist
}

Get a value, insert if not exist

var v = try my_hash_map.getOrPut(key);
if (!v.found_existing) {
    // We inserted an entry, specify the new value
    // This is a conditional in case creating the new value is expensive
    v.value_ptr.* = "my value";
}

var value = v.value_ptr.*; // use the value

You can find more APIs by going here and using your browser’s builtin search for pub fn.

About key data types

Zig hash map types start with the data type of the key:

  • std.StringHashMap - uses a good default hashing function for string keys
  • std.AutoHashMap - uses a good default hashing function for most data types
  • std.HashMap - the “bring your own hashing function” option

Note: AutoHashMap does not support slices, such as []const u8 string slices, because that is a pointer to an array and it is ambiguous whether or not you intend to hash the array elements or the pointer itself. You can use the generic std.HashMap for any slice type, you just have to provide your own hash functions.

Hashmaps are also sets

A set in Zig is just a hashmap with a void value:

var my_hash_map = std.AutoHashMap(K, void).init(allocator);

try my_hash_map.put(key, {}); // `{}` is a value of type `void`

Advanced usages

If you’re just getting started with Zig, don’t worry too much about the below. Just know that you have options available should you need to reduce memory usage or optimize your use of hashmaps in the future.

Managed vs. unmanaged hashmaps

You can add Unmanaged to the end of a Zig hashmap data type, e.g. std.StringHashMapUnmanaged in order to get the unmanaged version.

This merely doesn’t carry an allocator internally, instead you must pass the allocator into every method of the hashmap. While only a few bytes, this can be a useful optimization if you’re storing many hashmaps for example.

Managed:

var my_hash_map = std.StringHashMap(V).init(allocator);

Unmanaged:

var my_hash_map = std.StringHashMapUnmanaged(V){};

Array hash maps

Zig actually provides two hashmap implementations in the standard library

std.HashMap, perfect for every-day use cases:

  • Optimized for lookup times primarily
  • Optimized for insertion/removal times secondarily

std.ArrayHashMap, useful in some situations:

  • Iterating over the hashmap is an order of magnitude faster (a contiguous array)
  • Insertion order is preserved.
  • You can index into the underlying data like an array if you like
  • Deletions can be performed one of two ways, mirroring the ArrayList API:
    • swapRemove: swaps the target element with the last element in the list to remove it
    • orderedRemove: removes target element by shifting all elements forward, maintaining current ordering

Hashmap context

If you choose to use std.HashMap or std.ArrayHashMap directly (without the String or Auto prefix), then you’ll find it wants a context parameter and max load percentage:

var my_hash_map = std.HashMap(K, V, std.hash_map.AutoContext(K), std.hash_map.default_max_load_percentage);

The context parameter lets you embed some of your own data within the hash map type. This can be useful for reducing the amount of memory that a hash map takes up when doing a string table.

Pick your hashmap

Regular implementation:

Key typeManaged?How to initialize
Stringyesstd.StringHashMap(V).init(allocator)
Autoyesstd.AutoHashMap(K, V).init(allocator)
StringUnmanagedstd.StringHashMapUnmanaged(V){}
AutoUnmanagedstd.AutoHashMapUnmanaged(K, V){}

ArrayHashMap implementation:

Key typeManaged?How to initialize
Stringyesstd.StringArrayHashMap(V).init(allocator)
Autoyesstd.AutoArrayHashMap(K, V).init(allocator)
StringUnmanagedstd.StringArrayHashMapUnmanaged(V){}
AutoUnmanagedstd.AutoArrayHashMapUnmanaged(K, V){}

Learn more

The source code is very readable:

Help improve this page

I wrote this article quickly because I needed to explain my choice of hashmaps in the “Let’s build an Entity Component System from scratch” series and there was no better source of this info. I’m sure there are things that can be improved.

Feel free to send a PR!