std.uuid — UUIDs
Erzeugen, Einlesen und Vergleichen von UUIDs. Eine UUID besteht aus 16 Bytes; die Unit arbeitet durchgängig auf rohen Puffern, die der Aufrufer bereitstellt. Zusätzlich Umwandlung in die übliche Textform xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx und zurück.
Autor: Andreas Röne
Copyright: 2024–2025 Andreas Röne
Quelle: std/uuid.lyx
<WRAP info> Seit lyxc 1.0.20F arbeitet die Unit korrekt (#1473, #1474, #1475). Nachgemessen über zwei Programmläufe:
UUID 1 = A80673CB-6110-4E4C-97C3-53D3381EC76E Laenge 36
UUID 2 = 1B6600FA-037C-42C4-9DD3-E7FB475A1756
verschieden? 1
Variant = 1 Version = 4
zweiter Lauf:
UUID 1 = 96A9C854-4CF1-4266-8B30-1E4282917663
Behoben sind damit alle drei früheren Mängel: die Folge ist je Programmlauf verschieden (vorher fest einkompilierte Startsaat), ToString liefert 36 Zeichen statt 35, und GetVariant meldet 1 für RFC 4122.
</WRAP>
Import
import std.uuid;
Aufbau einer UUID
16 Bytes, in der Textform zu fünf Gruppen zusammengefasst:
550e8400-e29b-41d4-a716-446655440000
└──────┘ └──┘ └──┘ └──┘ └──────────┘
Byte 4-5 6-7 8-9 Byte 10-15
0-3 │ │
│ └── obere Bits: Variante (10 = RFC 4122)
└── obere 4 Bits: Version (4 = zufällig, 7 = zeitbasiert)
| Version | Herkunft der Bits | Eigenschaft |
|---|---|---|
| 4 | 122 Zufallsbits | keine Ordnung, keine Rückschlüsse auf Zeit oder Rechner |
| 7 | 48 Bit Unix-Zeit in Millisekunden, Rest zufällig | zeitlich sortierbar — günstig als Datenbankschlüssel |
Die restlichen 6 Bits sind in beiden Fällen durch Version und Variante belegt.
Funktionen
Einlesen und Prüfen
| Signatur | Beschreibung | Status |
|---|---|---|
FromString(str: pchar, output: pchar): bool | Liest die Textform in 16 Bytes ein; Bindestriche werden übersprungen | ok |
IsValid(str: pchar): bool | Prüft Länge (36 oder 32) und Hex-Zeichen | ok |
GetVersion(uuid: pchar): int64 | Version aus den oberen 4 Bit von Byte 6 | ok |
GetVariant(uuid: pchar): int64 | Variante aus Byte 8 | meldet nie RFC (#1475) |
IsNil(uuid: pchar): bool | Ob alle 16 Bytes 0 sind | ok |
Compare(a: pchar, b: pchar): int64 | Byteweiser Vergleich: -1, 0, 1 | ok |
SetNil(uuid: pchar): bool | Setzt alle 16 Bytes auf 0 | ok |
FromString nimmt die Form mit und ohne Bindestriche und akzeptiert Groß- wie Kleinschreibung. Beide Funktionen prüfen nur Form und Zeichenvorrat, nicht ob Version und Variante sinnvoll gesetzt sind.
Erzeugen und Ausgeben
| Signatur | Beschreibung | Status |
|---|---|---|
GenerateV4(output: pchar): bool | 16 Byte UUID v4 | wiederholbar (#1473) |
GenerateV7(output: pchar): bool | 16 Byte UUID v7 | ohne Zeitstempel (#1473, #1475) |
ToString(uuid: pchar, output: pchar): bool | 16 Bytes in die Textform | Format fehlerhaft (#1474) |
GenerateV4String(output: pchar): bool | v4 direkt als Text | erbt beide Fehler |
GenerateV7String(output: pchar): bool | v7 direkt als Text | erbt beide Fehler |
Konstante
| Name | Wert | Bedeutung |
|---|---|---|
UUID_SIZE | 16 | Bytes einer UUID |
Für die Textform sind 37 Bytes vorzusehen: 36 Zeichen plus abschließendes Nullbyte. Die vier UUID_VARIANT_*-Konstanten sind im Quelltext ohne pub deklariert und damit von außen nicht ansprechbar (#1475).
Beispiel: UUID einlesen und prüfen
import std.uuid;
import std.alloc;
fn main(): int64 {
// UUID aus dem Standardformat einlesen
var bytes: int64 := alloc(UUID_SIZE);
var text: pchar := "550e8400-e29b-41d4-a716-446655440000";
PrintLn("IsValid = ", IntToStr(IsValid(text) as int64));
PrintLn("FromString = ", IntToStr(FromString(text, bytes as pchar) as int64));
PrintLn("Version = ", IntToStr(GetVersion(bytes as pchar)));
PrintLn("IsNil = ", IntToStr(IsNil(bytes as pchar) as int64));
// Die 16 Bytes einzeln
Print("Bytes = ");
var i: int64 := 0;
while (i < UUID_SIZE) { Print(IntToStr(StrCharAt(bytes as pchar, i)), " "); i := i + 1; }
PrintLn("");
// Nil-UUID und Vergleich
var nil: int64 := alloc(UUID_SIZE);
SetNil(nil as pchar);
PrintLn("IsNil(nil) = ", IntToStr(IsNil(nil as pchar) as int64));
PrintLn("Compare(nil, uuid) = ", IntToStr(Compare(nil as pchar, bytes as pchar)));
PrintLn("Compare(uuid, uuid) = ", IntToStr(Compare(bytes as pchar, bytes as pchar)));
free(bytes, UUID_SIZE);
free(nil, UUID_SIZE);
return 0;
}
Ausgabe:
IsValid = 1
FromString = 1
Version = 4
IsNil = 0
Bytes = 85 14 132 0 226 155 65 212 167 22 68 102 85 68 0 0
IsNil(nil) = 1
Compare(nil, uuid) = -1
Compare(uuid, uuid) = 0
Compare vergleicht die Bytes in ihrer Reihenfolge. Bei UUID v7 entspricht das der zeitlichen Ordnung — sobald der Zeitstempel tatsächlich gesetzt wird (#1475).
Was nicht funktioniert
Geprüft mit lyxc 1.0.21A.
Jeder Programmlauf erzeugt dieselben UUIDs (#1473)
var s: int64 := alloc(40);
var i: int64 := 0;
while (i < 3) { GenerateV4String(s as pchar); PrintLn(s as pchar); i := i + 1; }
Zwei aufeinanderfolgende Aufrufe desselben Programms:
--- Lauf 1: --- Lauf 2:
DC0465AA-1FAD-4D5A-9AE5AC1B1E5F1370 DC0465AA-1FAD-4D5A-9AE5AC1B1E5F1370
796CFD10-FF19-4F60-9D04ACB41D022B46 796CFD10-FF19-4F60-9D04ACB41D022B46
78733AF2-DF5F-4EB7-8859D1EE3910CB48 78733AF2-DF5F-4EB7-8859D1EE3910CB48
Ursache ist ein eigener Zufallsgenerator mit fest einkompilierter Startsaat (SEED_INIT := 12345), der keine Systemquelle liest. Innerhalb eines Laufs sind die Werte verschieden — deshalb fällt es im Test mit einem einzelnen Prozess nicht auf. Zwei gleichzeitig laufende Dienste vergeben jedoch dieselben IDs.
Bis zum Fix die Zufallsbytes selbst besorgen. std.crypto.rand liest getrandom(2):
import std.crypto.rand;
import std.uuid;
fn EigeneV4(out: pchar): bool {
if (RandBytesExact(out as int64, 16) != 16) { return false; }
StrSetChar(out, 6, (StrCharAt(out, 6) & 15) | 64); // Version 4
StrSetChar(out, 8, (StrCharAt(out, 8) & 63) | 128); // Variante RFC 4122
return true;
}
Die Textform (behoben, #1474)
Eingabe : 550e8400-e29b-41d4-a716-446655440000
Ausgabe : 550E8400-E29B-41D4-A716446655440000
Laenge : 35
FromString(Ausgabe) = 0
Der vierte Bindestrich fehlt — ToString schreibt 8-4-4-16 statt 8-4-4-4-12. Zwei Folgen: der Rundlauf ist gebrochen (FromString verlangt mindestens 36 Zeichen und lehnt die eigene Ausgabe ab), und fremde Systeme weisen den Wert als ungültige UUID zurück.
Nebenbei gibt ToString Großbuchstaben aus; RFC 4122 verlangt für die Ausgabe Kleinschreibung. Beim Einlesen sind beide Formen erlaubt, das machen FromString und IsValid korrekt.
GetVariant meldet nie RFC 4122 (#1475)
Byte 8 = 167 (0xA7, obere Bits 101)
GetVariant = 0 (NCS — erwartet: RFC 4122)
Die Prüfreihenfolge fängt die RFC-Werte im ersten Zweig ab; der UUID_VARIANT_RFC-Zweig ist unerreichbarer Code. Auch selbst erzeugte UUIDs melden NCS, obwohl GenerateV4 die Variantenbits korrekt setzt. GetVersion arbeitet dagegen richtig.
UUID v7 ist nicht sortierbar (#1475)
Statt der Unix-Zeit stehen in den oberen 48 Bit die Werte des Pseudozufallsgenerators:
v7[0] = 28722F82-AE00-742D-...
v7[1] = 2017EF53-5201-74E7-... <- kleiner als der vorherige
v7[2] = 78ED86DA-0100-7686-...
Wer v7 wegen der Index-Lokalität in Datenbanken wählt, bekommt hier das Gegenteil. Der Zeitanteil ließe sich aus NowMs() in std.time bilden.
Speicherleck in den String-Varianten (#1475)
GenerateV4String und GenerateV7String legen intern 16 Byte an und geben sie nicht frei — pro Aufruf. In einem Dienst, der je Anfrage eine ID erzeugt, summiert sich das.
Verwandte Units
- std.crypto.rand — Zufallsbytes aus
getrandom(2) - std.time —
NowMs()für zeitbasierte Kennungen - std.base64 — kompaktere Textform für Kennungen
Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).
Letzte Aktualisierung: 2026-08-30 (Dateistand)
