5.0 KiB
5.0 KiB
std.StaticStringMap
Compile-time optimized lookup for small, fixed sets of string keys. Keys are grouped by length; lookup quickly rejects absent lengths and then compares candidates in the matching length group. This is not a perfect hash.
When to Use
- Keywords/reserved words lookup
- Command/option parsing
- Static configuration keys
- When string set is known at compile time
- Fast length-based narrowing, followed by a linear scan of keys with the queried length (plus string comparison cost)
Basic Usage
const std = @import("std");
const keywords = std.StaticStringMap(enum { @"if", @"else", @"while", @"for" }).initComptime(.{
.{ "if", .@"if" },
.{ "else", .@"else" },
.{ "while", .@"while" },
.{ "for", .@"for" },
});
// Lookup
if (keywords.get("while")) |kw| {
switch (kw) {
.@"while" => std.debug.print("found while\n", .{}),
else => {},
}
}
// Check existence
if (keywords.has("if")) {
// it's a keyword
}
Void Value (Set)
// When you only need presence check, use void
const reserved = std.StaticStringMap(void).initComptime(.{
.{"break"},
.{"continue"},
.{"return"},
.{"defer"},
});
if (reserved.has("break")) {
std.debug.print("'break' is reserved\n", .{});
}
ASCII Case-Insensitive Lookup
const commands = std.StaticStringMapWithEql(
i32,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "help", 1 },
.{ "quit", 2 },
.{ "list", 3 },
});
// All find the same entry
_ = commands.get("HELP"); // 1
_ = commands.get("Help"); // 1
_ = commands.get("help"); // 1
Runtime Initialization
// When data isn't known at comptime
const pairs = [_]struct { []const u8, i32 }{
.{ "one", 1 },
.{ "two", 2 },
.{ "three", 3 },
};
const map = try std.StaticStringMap(i32).init(&pairs, allocator);
defer map.deinit(allocator);
_ = map.get("two"); // 2
Runtime initialization copies the entry arrays and key slices, but not the bytes behind each key slice. Keep the key backing storage alive and unchanged for the lifetime of the map.
Get Index
// Get the index in the internal array
if (keywords.getIndex("if")) |idx| {
// idx is position in sorted-by-length array
}
Longest Prefix Match
const prefixes = std.StaticStringMap(u32).initComptime(.{
.{ "/api", 1 },
.{ "/api/v1", 2 },
.{ "/api/v2", 3 },
});
// Find longest matching prefix
if (prefixes.getLongestPrefix("/api/v2/users")) |kv| {
std.debug.print("matched: {s} -> {}\n", .{ kv.key, kv.value });
// matched: /api/v2 -> 3
}
Access All Keys/Values
const all_keys = keywords.keys(); // []const []const u8
const all_values = keywords.values(); // []const EnumType
Complete Example: HTTP Method Parser
const std = @import("std");
const Method = enum {
GET,
POST,
PUT,
DELETE,
PATCH,
HEAD,
OPTIONS,
};
const methods = std.StaticStringMap(Method).initComptime(.{
.{ "GET", .GET },
.{ "POST", .POST },
.{ "PUT", .PUT },
.{ "DELETE", .DELETE },
.{ "PATCH", .PATCH },
.{ "HEAD", .HEAD },
.{ "OPTIONS", .OPTIONS },
});
fn parseMethod(s: []const u8) ?Method {
return methods.get(s);
}
pub fn main() void {
if (parseMethod("POST")) |m| {
std.debug.print("Method: {}\n", .{m}); // Method: POST
}
if (parseMethod("INVALID")) |_| {
unreachable;
} else {
std.debug.print("Invalid method\n", .{});
}
}
Complete Example: Config Parser
const std = @import("std");
const ConfigKey = enum {
host,
port,
debug,
timeout,
};
const config_keys = std.StaticStringMapWithEql(
ConfigKey,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "host", .host },
.{ "port", .port },
.{ "debug", .debug },
.{ "timeout", .timeout },
});
fn parseConfig(line: []const u8) ?struct { key: ConfigKey, value: []const u8 } {
const eq_idx = std.mem.indexOf(u8, line, "=") orelse return null;
const key_str = std.mem.trim(u8, line[0..eq_idx], " ");
const value = std.mem.trim(u8, line[eq_idx + 1 ..], " ");
const key = config_keys.get(key_str) orelse return null;
return .{ .key = key, .value = value };
}
pub fn main() void {
const lines = [_][]const u8{
"HOST = localhost",
"PORT = 8080",
"Debug = true",
};
for (lines) |line| {
if (parseConfig(line)) |cfg| {
std.debug.print("{}: {s}\n", .{ cfg.key, cfg.value });
}
}
}
How It Works
- Strings are sorted by length at compile time
- Lookup first checks length to narrow candidates
- Only strings of matching length are compared
- Very efficient for disparate key lengths
Notes
- Comptime version has zero runtime allocation
- Keys are grouped by length for fast rejection
- Use
eqlAsciiIgnoreCasefor case-insensitive matching - Runtime
init()requiresdeinit()to free memory - Best for small to medium-sized static string sets
- Not suitable for dynamic key sets (use HashMap instead)