# Why StringHashMap Exists

### StringHashMap

A `StringHashMap` is a hash map where the key is a string.

In Zig, a string is usually a slice of bytes:

```zig
[]const u8
```

So this:

```zig
std.StringHashMap(u32)
```

means:

```text
string -> u32
```

Examples:

```text
"alice" -> 120
"bob" -> 95
"charlie" -> 180
```

Use `StringHashMap` when you want to look up values by text.

## Why StringHashMap Exists

You already saw `AutoHashMap`.

For number keys, this is fine:

```zig
std.AutoHashMap(u32, []const u8)
```

But strings need special handling.

A string key is not a single value. It is a slice:

```zig
[]const u8
```

A slice contains:

```text
pointer + length
```

To compare two strings, Zig must compare their bytes.

These two strings should be considered equal:

```zig
const a = "zig";
const b = "zig";
```

Even if they live at different addresses, their bytes are the same:

```text
z i g
z i g
```

`StringHashMap` handles this correctly.

It hashes and compares string contents, not just pointer addresses.

## Creating a StringHashMap

Here is a complete empty map:

```zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    var scores = std.StringHashMap(u32).init(allocator);
    defer scores.deinit();
}
```

This creates a map from strings to unsigned integers.

```text
key type   = []const u8
value type = u32
```

The allocator is needed because the hash map stores an internal table on the heap.

## Adding Values

Use `put`:

```zig
try scores.put("alice", 120);
try scores.put("bob", 95);
try scores.put("charlie", 180);
```

Full example:

```zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    var scores = std.StringHashMap(u32).init(allocator);
    defer scores.deinit();

    try scores.put("alice", 120);
    try scores.put("bob", 95);
    try scores.put("charlie", 180);
}
```

`put` uses `try` because inserting may allocate memory.

Allocation can fail, so the error must be handled.

## Looking Up a Value

Use `get`:

```zig
const score = scores.get("alice");
```

The result is optional:

```zig
?u32
```

The key might not exist.

A safe lookup looks like this:

```zig
if (scores.get("alice")) |score| {
    std.debug.print("alice = {}\n", .{score});
} else {
    std.debug.print("alice not found\n", .{});
}
```

Output:

```text
alice = 120
```

If the key does not exist:

```zig
if (scores.get("david")) |score| {
    std.debug.print("david = {}\n", .{score});
} else {
    std.debug.print("david not found\n", .{});
}
```

Output:

```text
david not found
```

## Updating a Value

Calling `put` with the same key replaces the old value.

```zig
try scores.put("alice", 120);
try scores.put("alice", 150);
```

Now:

```text
"alice" -> 150
```

For simple values like integers, this is easy.

For values that own heap memory, you must free the old value yourself before replacing it, or use a pattern that lets you inspect the old value.

## Counting Words

A common use for `StringHashMap` is counting words.

Input:

```zig
const words = [_][]const u8{
    "zig",
    "c",
    "zig",
    "rust",
    "zig",
    "c",
};
```

Expected result:

```text
zig  -> 3
c    -> 2
rust -> 1
```

One simple version:

```zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    const words = [_][]const u8{
        "zig",
        "c",
        "zig",
        "rust",
        "zig",
        "c",
    };

    var counts = std.StringHashMap(u32).init(allocator);
    defer counts.deinit();

    for (words) |word| {
        const old_count = counts.get(word) orelse 0;
        try counts.put(word, old_count + 1);
    }

    var it = counts.iterator();
    while (it.next()) |entry| {
        std.debug.print("{s}: {}\n", .{
            entry.key_ptr.*,
            entry.value_ptr.*,
        });
    }
}
```

The key line is:

```zig
const old_count = counts.get(word) orelse 0;
```

This means:

```text
use the old count if the word exists
otherwise use 0
```

Then this stores the new count:

```zig
try counts.put(word, old_count + 1);
```

## Counting with getOrPut

A more efficient pattern uses `getOrPut`.

```zig
const result = try counts.getOrPut(word);
```

This does two jobs:

1. look for the key
2. insert it if missing

Then we initialize the value only when the key is new:

```zig
if (!result.found_existing) {
    result.value_ptr.* = 0;
}

result.value_ptr.* += 1;
```

Full loop:

```zig
for (words) |word| {
    const result = try counts.getOrPut(word);

    if (!result.found_existing) {
        result.value_ptr.* = 0;
    }

    result.value_ptr.* += 1;
}
```

This is a common Zig pattern.

The value pointer points into the map. Writing through it changes the stored value directly.

## Iterating Over StringHashMap

Use an iterator:

```zig
var it = scores.iterator();

while (it.next()) |entry| {
    std.debug.print("{s}: {}\n", .{
        entry.key_ptr.*,
        entry.value_ptr.*,
    });
}
```

Each `entry` gives pointers:

```zig
entry.key_ptr
entry.value_ptr
```

To read the stored values, use `.*`:

```zig
entry.key_ptr.*
entry.value_ptr.*
```

For a string key, print with `{s}`:

```zig
std.debug.print("{s}\n", .{entry.key_ptr.*});
```

For a number value, print with `{}`:

```zig
std.debug.print("{}\n", .{entry.value_ptr.*});
```

## Iteration Order Is Not Sorted

A `StringHashMap` does not remember insertion order.

If you insert:

```text
alice
bob
charlie
```

The iterator may return:

```text
bob
charlie
alice
```

or another order.

Do not depend on hash map iteration order.

If you need sorted output, collect the keys into an array and sort them.

If you need insertion order, keep a separate `ArrayList` of keys.

## Removing Keys

Use `remove`:

```zig
const removed = scores.remove("bob");
```

The result is a boolean:

```text
true  = key existed and was removed
false = key was not found
```

Example:

```zig
if (scores.remove("bob")) {
    std.debug.print("removed bob\n", .{});
} else {
    std.debug.print("bob not found\n", .{});
}
```

## Contains

Use `contains` when you only need to know whether the key exists:

```zig
if (scores.contains("alice")) {
    std.debug.print("alice exists\n", .{});
}
```

Use `get` when you need the value.

## String Literals as Keys

This is safe:

```zig
try scores.put("alice", 120);
```

String literals live for the whole program.

The map stores the slice:

```zig
"alice"
```

It does not need to copy the bytes.

This is fine for static keys like command names, keywords, fixed labels, and test data.

## Temporary Strings as Keys

Be careful with temporary strings.

This is dangerous:

```zig
var buffer: [32]u8 = undefined;
const name = try std.fmt.bufPrint(&buffer, "user-{d}", .{1});

try scores.put(name, 120);
```

The key points into `buffer`.

That is okay only while `buffer` remains valid and unchanged.

If you later reuse the buffer, the map key may silently change.

Example:

```zig
const a = try std.fmt.bufPrint(&buffer, "alice", .{});
try scores.put(a, 120);

const b = try std.fmt.bufPrint(&buffer, "bob", .{});
try scores.put(b, 95);
```

Both keys may refer to the same buffer memory.

That is not what you want.

## Owning String Keys

When keys are created dynamically, usually you should copy them into heap memory.

Use `allocator.dupe`:

```zig
const owned_name = try allocator.dupe(u8, name);
try scores.put(owned_name, 120);
```

Now the map stores a key whose bytes live on the heap.

But this creates a new responsibility.

You must free that memory later.

## Freeing Owned Keys

`deinit()` frees the map’s internal hash table.

It does not automatically free heap memory used by your keys.

If the map owns its keys, free them before calling `deinit()`:

```zig
var it = scores.iterator();
while (it.next()) |entry| {
    allocator.free(entry.key_ptr.*);
}

scores.deinit();
```

In that case, do not also use:

```zig
defer scores.deinit();
```

unless you make sure the owned keys are freed before it runs.

A clean pattern is:

```zig
defer {
    var it = scores.iterator();
    while (it.next()) |entry| {
        allocator.free(entry.key_ptr.*);
    }
    scores.deinit();
}
```

This puts the cleanup logic in one place.

## Owning String Values

The same idea applies to values.

Example:

```zig
var users = std.StringHashMap([]u8).init(allocator);
```

Here the values are mutable byte slices. They may point to heap memory.

If the map owns the values, you must free them:

```zig
defer {
    var it = users.iterator();
    while (it.next()) |entry| {
        allocator.free(entry.value_ptr.*);
    }
    users.deinit();
}
```

If both keys and values are owned, free both:

```zig
defer {
    var it = users.iterator();
    while (it.next()) |entry| {
        allocator.free(entry.key_ptr.*);
        allocator.free(entry.value_ptr.*);
    }
    users.deinit();
}
```

This is normal Zig. Ownership is explicit.

## Replacing Owned Values

Be careful with this:

```zig
try users.put("alice", try allocator.dupe(u8, "first"));
try users.put("alice", try allocator.dupe(u8, "second"));
```

The second `put` replaces the first value.

If you do not free the first value, it leaks.

A safer pattern is to check first:

```zig
if (users.get("alice")) |old_value| {
    allocator.free(old_value);
}

try users.put("alice", try allocator.dupe(u8, "second"));
```

This frees the old value before storing the new one.

## Pointers Into the Map

`getOrPut` gives you pointers into the map.

```zig
const result = try scores.getOrPut("alice");
const value_ptr = result.value_ptr;
```

Use these pointers immediately.

Do not keep them across operations that may grow the map:

```zig
const result = try scores.getOrPut("alice");
const value_ptr = result.value_ptr;

try scores.put("bob", 95);

value_ptr.* = 999; // dangerous
```

The `put` may resize the map. If the map resizes, old entry pointers may become invalid.

## StringHashMap vs StringArrayHashMap

Zig also has ordered map variants, such as `std.StringArrayHashMap`.

The basic difference:

| Type | Keeps insertion order? | Lookup by string? |
|---|---:|---:|
| `std.StringHashMap(V)` | No | Yes |
| `std.StringArrayHashMap(V)` | Yes | Yes |

Use `StringHashMap` when lookup speed matters and order does not matter.

Use `StringArrayHashMap` when you need stable iteration order.

## Practical Example: Environment-Like Table

Suppose you want to store configuration values:

```text
host -> localhost
port -> 8080
mode -> debug
```

You can write:

```zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    var config = std.StringHashMap([]const u8).init(allocator);
    defer config.deinit();

    try config.put("host", "localhost");
    try config.put("port", "8080");
    try config.put("mode", "debug");

    if (config.get("port")) |port| {
        std.debug.print("port = {s}\n", .{port});
    }
}
```

Output:

```text
port = 8080
```

This is a natural use of `StringHashMap`: small string keys and simple values.

## Practical Example: Keyword Table

Compilers and parsers often use string maps.

Example:

```zig
const TokenKind = enum {
    keyword_fn,
    keyword_var,
    keyword_const,
    identifier,
};

var keywords = std.StringHashMap(TokenKind).init(allocator);
defer keywords.deinit();

try keywords.put("fn", .keyword_fn);
try keywords.put("var", .keyword_var);
try keywords.put("const", .keyword_const);
```

Then, when reading an identifier:

```zig
const kind = keywords.get(text) orelse .identifier;
```

This means:

```text
if text is a keyword, use its keyword token kind
otherwise, treat it as an identifier
```

This pattern is common in lexers.

## Common Beginner Mistakes

The first mistake is using `AutoHashMap([]const u8, V)` when you really want string-content comparison. Use `StringHashMap(V)` for string keys.

The second mistake is storing keys that point into temporary buffers.

The third mistake is forgetting to free owned keys or owned values.

The fourth mistake is depending on iteration order.

The fifth mistake is keeping `entry.value_ptr` or `entry.key_ptr` after inserting more items into the map.

## A Useful Mental Model

A `StringHashMap` is a lookup table from text to data.

```text
text -> value
```

Examples:

```text
"alice" -> User
"main.zig" -> FileInfo
"while" -> TokenKind.keyword_while
"Content-Type" -> "application/json"
```

The map compares strings by their bytes.

The map owns its internal table.

Your program decides who owns the string data used as keys and values.

## Summary

Use `std.StringHashMap(V)` when your keys are strings.

It is one of the most common containers in Zig programs.

The main rules are:

Use string literals freely when they live for the whole program.

Copy temporary strings into owned memory before storing them.

Free owned keys and values yourself.

Do not depend on iteration order.

Do not keep pointers into the map across inserts or other operations that may resize it.

