現在のブログ
ゲーム開発ブログ (2025年~) Gamedev Blog (2025~)
レガシーブログ
テクノロジーブログ (2018~2024年) リリースノート (2023~2025年) MeatBSD (2024年)
【C/Go/Zig】Implementing a UUID Generator from Scratch
Today I want to implement a UUID generator together in three programming languages (C, Go, and Zig).
The reason is simple: many existing UUID generators are unnecessarily large.
There are too many options, and I just want "run the tool and get a UUID immediately".
What is UUID?
UUID is a long identifier string that you see more and more often on the internet.
It is 128 bits long and consists of 32 hexadecimal characters separated by hyphens:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
UUID has multiple versions:
- Version 1: Time-based (Gregorian calendar), time-ordered, general purpose, generated based on MAC address + timestamp.
- Version 2: For DCE Security UUIDs, often omitted because it is not defined in the standard.
- Version 3: Name-based, deterministic, content-derived, generated based on MD5 hash.
- Version 4: Completely random, general purpose, generated from random bytes.
- Version 5: Name-based, deterministic, content-derived, generated based on SHA1 hash.
- Version 6: Time-based (Gregorian calendar), time-ordered, better for time sorting, a rearranged version of Version 1.
- Version 7: Time-based (Unix Epoch), time-ordered, for modern applications, based on Unix timestamp.
- Version 8: Currently experimental for vendor-specific use cases.
Versions 9–15 are reserved for future definitions.
Version 0 is unused.
Version 7 is considered modern, but Version 4 is optimal for randomness.
Therefore, I will implement only Version 4.
In Version 4, only the two digits for version and variant need to be fixed.
The rest can be completely random.
About the variant field:
- 1–7: For NCS (Network Computing System) backward compatibility, including Nil UUID.
- C and D: For Microsoft backward compatibility.
- E and F: For future definitions, including Max UUID.
Therefore, only 8, 9, A, B remain.
C
#include <stdio.h>
#include <stdint.h>
#if defined(_WIN64)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <bcrypt.h>
#endif
int main(void) {
uint8_t data[16];
#if defined(_WIN64)
NTSTATUS n = BCryptGenRandom(NULL, data, sizeof(data),
BCRYPT_USE_SYSTEM_PREFERRED_RNG);
#else
FILE *fp = fopen("/dev/urandom", "rb");
fread(data, 1, sizeof(data), fp);
fclose(fp);
#endif
data[6] = (data[6] & 0x0F) | 0x40;
data[8] = (data[8] & 0x3F) | 0x80;
char out[37];
snprintf(out, 37,
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
data[0], data[1], data[2], data[3],
data[4], data[5],
data[6], data[7],
data[8], data[9],
data[10], data[11], data[12], data[13], data[14], data[15]);
printf("%s\n", out);
return 0;
}
BSD, Linux, macOS, Illumos
$ cc -O3 uuid.c -o uuid -static
$ strip uuid
$ ls -thal uuid
-rwxr-xr-x 1 suwako suwako 691K 6月 14 15:05 uuid
$ ./uuid
dd9a18ea-6485-4827-95fe-34a414e54f61
$ ./uuid
6a755d71-e2c3-4908-9b5b-1b1cac9f1dbf
$ ./uuid
d18d8d7c-ed9a-4a43-b726-40aa4e2ac005
$ ./uuid
3514aab1-68b6-4df5-ae0a-3bbaba572262
$ ./uuid
54da7079-a5a9-48b7-ac04-40a9d58d1ac0
$ ./uuid
a16ba8a1-567d-4d1c-acb0-7cfd7da1ed85
$ ./uuid
3296c8da-8a27-4afb-b53b-0805f5aca55a
$ ./uuid
6ca79551-131c-400b-8aa5-65176a26038a
$ ./uuid
56d059b9-9536-4b20-9df6-7a75d637410b
$ ./uuid
55b1648e-590d-4ecc-a505-a7b21a4dd52e
Windows
> cl.exe uuid.c /link bcrypt.lib /out:uuid.exe
Microsoft (R) C/C++ Optimizing Compiler Version 19.43.34810 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
uuid.c
Microsoft (R) Incremental Linker Version 14.43.34810.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:uuid.exe
bcrypt.lib
/out:uuid.exe
uuid.obj
> .\uuid.exe
a0a0bc9e-9243-46e5-82f4-95b48c4baa78
> .\uuid.exe
6f6c839f-fc3b-47ef-8c8e-5fd0554793b2
> .\uuid.exe
07762170-dcdc-4e49-b127-e87c01462487
> .\uuid.exe
af35e21e-b030-4504-9ad3-99dfefdf373f
> .\uuid.exe
5f105a97-e6f7-483a-9058-963d56af3cc8
> .\uuid.exe
032763af-7db9-468b-be58-58fea4711d59
> .\uuid.exe
89205d97-e78d-4b32-b113-4c9f909f6149
> .\uuid.exe
79206b73-6c7e-43ba-81d1-2bc316ff3641
> .\uuid.exe
675150e6-983b-4018-83d7-d3444ec7cbd1
> .\uuid.exe
a9a0d60c-f912-4a6b-9d01-db3e290e14a9
Go
package main
import (
"crypto/rand"
"fmt"
)
func main() {
data := make([]byte, 16)
_, err := rand.Read(data)
if err != nil {
fmt.Println(err)
return
}
data[6] = (data[6] & 0x0F) | 0x40
data[8] = (data[8] & 0x3F) | 0x80
fmt.Printf(
"%x-%x-%x-%x-%x\n",
data[0:4],
data[4:6],
data[6:8],
data[8:10],
data[10:16],
)
}
$ go build -ldflags "-w -s" uuid.go
$ strip uuid
$ ls -thal uuid
-rwxr-xr-x 1 suwako suwako 1.7M 6月 14 15:09 uuid
$ ./uuid
d3ba3a2a-5378-4eb5-b1d2-b11f1bb37114
$ ./uuid
39b3ef52-1042-407d-8184-30fb9b7db00e
$ ./uuid
fd801a2c-c797-4568-a189-c0462ceb86cf
$ ./uuid
93d39072-169f-4513-8004-ab160526ab88
$ ./uuid
9e5ef1c0-9256-41ec-a4d8-faea557d8c10
$ ./uuid
62e26a1e-1796-451e-9c44-af34ed37526f
$ ./uuid
d71ed1a1-8518-42e8-b935-a7299ebb966c
$ ./uuid
a6143ba7-431f-475a-babc-b60a45e9ab45
Zig
const std = @import("std");
pub fn main(init: std.process.Init) !void {
var data: [16]u8 = undefined;
init.io.random(&data);
data[6] = (data[6] & 0x0F) | 0x40;
data[8] = (data[8] & 0x3F) | 0x80;
const hex = std.fmt.bytesToHex(&data, .lower);
const id = try std.fmt.allocPrint(init.gpa, "{s}-{s}-{s}-{s}-{s}", .{
hex[0..8],
hex[8..12],
hex[12..16],
hex[16..20],
hex[20..32],
});
defer init.gpa.free(id);
std.debug.print("{s}\n", .{ id });
}
$ zig build-exe uuid.zig -Doptimize=ReleaseSmall -static
$ strip uuid
$ ls -thal uuid
-rwxr-xr-x 1 suwako suwako 2.8M 6月 14 15:07 uuid
$ ./uuid
eb86adcf-2368-4413-b75e-373cc972a1df
$ ./uuid
77b2aec4-3ae5-43a4-aa2b-3945dc261300
$ ./uuid
5dac8e99-2412-4aa4-8ec6-64ee0a3cf52f
$ ./uuid
6794f31b-c946-409a-8fa0-2461573978cf
$ ./uuid
a7c78aaf-2fc8-4043-a891-8964e1522d3a
$ ./uuid
d9468118-8a63-4cec-98e0-aea278a7fdf3
$ ./uuid
8cbe53e2-0086-47d7-8fc6-0fe048f3dea4
$ ./uuid
c39cbef6-58b3-4945-b845-5c7d2b5a86f7
$ ./uuid
c018d10f-02aa-4a19-99e9-ac2ab86cfbc1
$ ./uuid
417cf67f-cbfa-4032-8642-480d1e9b9e8c
$ ./uuid
c26da70a-fcea-4fdb-8a68-f8b6e4809231
PHP
As a bonus, since Little Beast implements it in exactly the same way, here is how it works in PHP as well.
<?php
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
echo vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4))."\n";
?>
$ ls -thal uuid
-rw-r--r-- 1 suwako suwako 207B 6月 14 15:21 uuid
$ php ./uuid
53edec61-db8c-4d40-a83c-4acb70a5bc19
$ php ./uuid
35f8d144-4220-474e-bdf2-da162114d774
$ php ./uuid
841d7e8d-1eff-4dc2-afa8-217030d08eeb
$ php ./uuid
2e4caea8-7056-404a-9c3f-9ab3ce2ab8ff
$ php ./uuid
b1c91017-da8a-4988-8ecd-b8f8e7f834b6
$ php ./uuid
3a693006-ce03-43ec-84fe-5b1923fb4dab
$ php ./uuid
cf776682-9968-4fb2-a1c7-10311dc98742
$ php ./uuid
a7eb8a23-8445-4930-88f4-7bb4233ddcd0
Conclusion
With that, we now have UUIDv4 generators written as simply as possible in four different languages.
No more need for huge programs, a simple generator is enough.
Source code:
That's all