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 keysstd.AutoHashMap
- uses a good default hashing function for most data typesstd.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 itorderedRemove
: 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 type | Managed? | How to initialize |
---|---|---|
String | yes | std.StringHashMap(V).init(allocator) |
Auto | yes | std.AutoHashMap(K, V).init(allocator) |
String | Unmanaged | std.StringHashMapUnmanaged(V){} |
Auto | Unmanaged | std.AutoHashMapUnmanaged(K, V){} |
ArrayHashMap
implementation:
Key type | Managed? | How to initialize |
---|---|---|
String | yes | std.StringArrayHashMap(V).init(allocator) |
Auto | yes | std.AutoArrayHashMap(K, V).init(allocator) |
String | Unmanaged | std.StringArrayHashMapUnmanaged(V){} |
Auto | Unmanaged | std.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.