std.uuid — UUIDs

→ Zurück zur Unit-Übersicht

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

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)