A fast, allocation-free implementation of ULID (Universally Unique Lexicographically Sortable Identifier) in Zig 0.15.1.
Lexicographically sortable, 128-bit compatible, with monotonic + atomic generators.
Generated by AI according to the ULID specification
Comprehensive compliance tests can be found in spec_compliance_test.zig
const ulid = @import("ulid");
const id = ulid.new().toString(); // "01HQXW5P7R8ZYFG9K3NMVBCXSD"- Fast & allocation-free — stack-allocated
[26]u8strings, no heap needed - 128-bit compatible — drop-in replacement for UUID columns
- Crockford's Base32 — case-insensitive, avoids ambiguous I/L/O/U
- Monotonic generation — strictly increasing within the same millisecond
- Thread-safe — lock-free AtomicGenerator for high-concurrency scenarios
- Pure Zig — zero dependencies, works out of the box
Add to your build.zig.zon:
zig fetch --save https://github.com/nicolaou-dev/ulid.zig/archive/refs/heads/main.tar.gzIn your build.zig:
const ulid_dep = b.dependency("ulid", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("ulid", ulid_dep.module("ulid"));const ulid = @import("ulid");
// Generate new ULID
const id = ulid.new();
const str = id.toString(); // [26]u8Generate string directly:
const id_str = ulid.newString(); // [26]u8Guarantees strictly increasing values within the same millisecond:
const id1 = try ulid.newMonotonic();
const id2 = try ulid.newMonotonic();
std.debug.assert(ulid.Ulid.lessThan(id1, id2));const parsed = try ulid.parse("01HQXW5P7R8ZYFG9K3NMVBCXSD");
const ts = parsed.timestamp(); // ms since epoch
// Case-insensitive parsing (output always canonical uppercase)
const lower = try ulid.parse("01hqxw5p7r8zyfg9k3nmvbcxsd");
// Validation without parsing
if (ulid.isValid("01HQXW5P7R8ZYFG9K3NMVBCXSD")) {
const timestamp = try ulid.extractTimestamp("01HQXW5P7R8ZYFG9K3NMVBCXSD");
}
// Compile-time parsing (no error handling needed)
const known = ulid.parseComptime("01HQXW5P7R8ZYFG9K3NMVBCXSD");For hot paths, encode directly into caller's buffer:
var buffer: [26]u8 = undefined;
ulid.newInto(&buffer);var gen = ulid.Generator.init();
const id = try gen.next();const min_ulid = ulid.minForTime(ts_ms); // All zeros entropy
const max_ulid = ulid.maxForTime(ts_ms); // All ones entropy
// Check if ULID is within time range
if (ulid.isInTimeRange(id, start_ms, end_ms)) {
// ...
}Create ULIDs with specific components:
var builder = ulid.Builder{};
const id = builder.withTimestamp(1234567890).build();
// Or with custom entropy
const entropy = [_]u8{1,2,3,4,5,6,7,8,9,10};
const id2 = builder
.withTimestamp(ts)
.withEntropy(entropy)
.build();var ulids: [100]ulid.Ulid = ...;
ulid.sort(&ulids);
// Or sort string representations
var strings: [100][]const u8 = ...;
ulid.sortStrings(&strings);For high-concurrency scenarios:
var gen = ulid.AtomicGenerator.init();
const id1 = try gen.next(); // Thread-safe
const id2 = try gen.next();if (ulid.Ulid.lessThan(id1, id2)) {
// id1 was created before id2
}
if (ulid.Ulid.equals(id1, id2)) {
// Same ULID
}// Parsing errors
ulid.parse("invalid") catch |err| switch (err) {
error.InvalidUlid => // Wrong format/length
};
// Monotonic overflow (extremely rare)
ulid.newMonotonic() catch |err| switch (err) {
error.MonotonicOverflow => // All 80 entropy bits exhausted in 1ms
}; 01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Randomness
48 bits 80 bits
- 48-bit timestamp — ms precision (~8,919 years)
- 80-bit randomness — 1.21e+24 unique IDs per ms
- Encoding — Crockford's Base32 (no I, L, O, U)
- Length — 26 chars, fixed width
- Generation: < 200ns per ULID
- No heap allocations for basic operations
- Thread-safe atomic generator available
- Optimized for both single and high-volume generation
Run the benchmark tests yourself:
zig build test- Shorter strings (26 vs 36 chars)
- Sortable by creation time
- Easier to read, avoids ambiguous characters
- Still 128-bit compatible with UUID storage columns
| Feature | ULID | UUID v4 |
|---|---|---|
| Size | 128 bits | 128 bits |
| Sortable | ✓ (by time) | ✗ Random |
| String length | 26 chars | 36 chars |
| Timestamp | ✓ (48-bit ms) | ✗ None |
| Monotonic | ✓ | ✗ |
MIT
