This Go package provides tools for generating and parsing Universally Unique Identifiers (UUIDs) according to the RFC 9562 standard, which obsoletes RFC 4122.
- Full RFC 9562 Implementation: Supports UUID versions 1, 3, 4, 5, 6, 7, and 8.
- Thread-Safe with Monotonicity Guarantees: V1 and V6 provide strict in-process monotonicity under concurrent generation. V7 is timestamp-ordered with 12-bit sub-millisecond precision.
- Monotonic Process-Local Clock: Time-based UUIDs (V1, V6, V7) derive timestamps from a monotonic process-local clock that is resilient to wall-clock adjustments. Timestamps may diverge slightly from wall-clock time but never go backward.
- Sortable UUIDs: V6 and V7 provide time-sortable UUIDs, ideal for database keys. V7 is generally recommended for new applications.
- High-Precision V7: Version 7 implementation uses a 48-bit millisecond timestamp plus a 12-bit sub-millisecond fraction (RFC 9562 Method 3) for ordering within the same millisecond.
- Configurable V1 Node ID: Use system hardware MAC, a custom MAC, or the default randomly generated node ID for V1 UUIDs. V6 fills its trailing fields with
crypto/randby default but respects an explicitly configured node ID. - Robust Parsing: Parse canonical string representation or raw binary bytes.
- Standard Interface Support: Natively implements
fmt.Stringer,encoding.Text(Un)Marshaler,encoding.Binary(Un)Marshaler,database/sql.Scanner, anddatabase/sql/driver.Valuerfor seamless integration.
go get github.com/fossoreslp/uuid@v1.0.0Requires Go 1.25 or later.
package main
import (
"fmt"
"github.com/fossoreslp/uuid"
)
func main() {
// Version 4 (Random)
idV4 := uuid.NewV4()
fmt.Printf("UUIDv4: %s\n", idV4)
// Version 7 (Sortable Timestamp + Random, Recommended)
idV7 := uuid.NewV7()
fmt.Printf("UUIDv7: %s\n", idV7)
// Version 1 (Timestamp + Node ID)
idV1 := uuid.NewV1()
fmt.Printf("UUIDv1: %s\n", idV1)
// Version 6 (Sortable Timestamp + Random)
idV6 := uuid.NewV6()
fmt.Printf("UUIDv6: %s\n", idV6)
// Version 3 (MD5 Hash)
idV3 := uuid.NewV3(uuid.NamespaceDNS(), "example.com")
fmt.Printf("UUIDv3: %s\n", idV3)
// Version 5 (SHA1 Hash)
idV5 := uuid.NewV5(uuid.NamespaceDNS(), "example.com")
fmt.Printf("UUIDv5: %s\n", idV5)
// Version 8 (Custom Data)
customData := []byte{
0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE,
0xFE, 0xED, 0xFA, 0xCE, 0xBA, 0xAD, 0xF0, 0x0D,
}
idV8 := uuid.NewV8(customData)
fmt.Printf("UUIDv8: %s\n", idV8) // Note: Version/Variant bits are overwritten
}package main
import (
"fmt"
"github.com/fossoreslp/uuid"
)
func main() {
// Parse from string
s := "1ec9414c-232a-6b00-b3c8-9f6bdeced846" // A UUIDv6 example
id, err := uuid.Parse(s)
if err != nil {
fmt.Printf("Error parsing string: %v\n", err)
} else {
fmt.Printf("Parsed from string: %s (Version %d)\n", id, id.Version())
}
// Parse from binary bytes (e.g., from database)
binaryBytes := []byte{
0x01, 0x7f, 0x22, 0xe2, 0x79, 0xb0, // Timestamp (ms)
0x7c, 0xc3, // Version 7 + rand_a
0x98, 0xc4, 0xdc, 0x0c, 0x0c, 0x07, 0x39, 0x8f, // Variant + rand_b
} // A UUIDv7 example
idFromBin, err := uuid.ParseBytes(binaryBytes)
if err != nil {
fmt.Printf("Error parsing binary bytes: %v\n", err)
} else {
fmt.Printf("Parsed from binary: %s (Version %d)\n", idFromBin, idFromBin.Version())
}
// Parse from string bytes (e.g., from database storing as CHAR(36))
stringBytes := []byte("919108f7-52d1-4320-9bac-f847db4148a8") // A UUIDv4 example
idFromStringBytes, err := uuid.ParseBytes(stringBytes)
if err != nil {
fmt.Printf("Error parsing string bytes: %v\n", err)
} else {
fmt.Printf("Parsed from string bytes: %s (Version %d)\n", idFromStringBytes, idFromStringBytes.Version())
}
}package main
import (
"fmt"
"github.com/fossoreslp/uuid"
)
func main() {
nilUUID := uuid.UUID{} // Zero value is the Nil UUID
maxUUID := uuid.UUID{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} // All bits set to 1
fmt.Printf("Is %s Nil? %t\n", nilUUID, nilUUID.IsNil()) // true
fmt.Printf("Is %s Max? %t\n", maxUUID, maxUUID.IsMax()) // true
idV4 := uuid.NewV4()
fmt.Printf("Is %s Nil? %t\n", idV4, idV4.IsNil()) // false
fmt.Printf("Is %s Max? %t\n", idV4, idV4.IsMax()) // false
}The built-in interface support makes common tasks easy:
package main
import (
"database/sql"
"encoding/json"
"fmt"
"github.com/fossoreslp/uuid"
_ "github.com/mattn/go-sqlite3" // Example DB driver
)
type Record struct {
ID uuid.UUID `json:"id" db:"id"`
Name string `json:"name" db:"name"`
}
func main() {
id := uuid.NewV7()
// fmt.Stringer (used by Println, etc.)
fmt.Printf("UUID: %s\n", id)
// encoding.Text(Un)Marshaler / encoding.Binary(Un)Marshaler (e.g., JSON)
rec := Record{ID: id, Name: "Example"}
jsonData, _ := json.Marshal(rec)
fmt.Printf("JSON: %s\n", jsonData) // Output: {"id":"01...","name":"Example"}
var decodedRec Record
json.Unmarshal(jsonData, &decodedRec)
fmt.Printf("Decoded ID: %s\n", decodedRec.ID)
// database/sql.Scanner / driver.Valuer (Database interaction)
// Assuming a DB connection `db *sql.DB` and table `records (id BLOB PRIMARY KEY, name TEXT)`
// db, _ := sql.Open("sqlite3", ":memory:")
// db.Exec("CREATE TABLE records (id BLOB PRIMARY KEY, name TEXT)")
// _, err := db.Exec("INSERT INTO records (id, name) VALUES (?, ?)", id, "DB Example")
// if err == nil {
// var dbRec Record
// row := db.QueryRow("SELECT id, name FROM records WHERE id = ?", id)
// err = row.Scan(&dbRec.ID, &dbRec.Name) // Scan automatically handles uuid.UUID
// if err == nil {
// fmt.Printf("Read from DB: ID=%s, Name=%s\n", dbRec.ID, dbRec.Name)
// }
// }
}Call these functions early in your application initialization, before generating V1 or V6 UUIDs. They are not thread-safe during configuration.
V1 uses a randomly generated node ID by default. V6 fills its trailing fields entirely from crypto/rand by default and does not embed a node ID unless one is explicitly configured.
package main
import (
"fmt"
"github.com/fossoreslp/uuid"
"log"
"net"
)
func main() {
// Option 1: Try to use a hardware MAC address from the system
err := uuid.UseHardwareMAC()
if err != nil {
log.Printf("Could not set hardware MAC, using random: %v", err)
// Default random node ID will be used
} else {
fmt.Println("Using hardware MAC address for V1/V6.")
}
// Option 2: Set a specific MAC address
// customMAC, _ := net.ParseMAC("01:02:03:04:05:06")
// err = uuid.SetMACAddress(customMAC)
// if err != nil {
// log.Fatalf("Failed to set custom MAC: %v", err)
// }
// fmt.Println("Using custom MAC address for V1/V6.")
// Now generate V1 or V6 UUIDs
idV1 := uuid.NewV1()
fmt.Printf("Generated V1 with configured node ID: %s\n", idV1)
}- Version 1 (Timestamp, Node ID): Based on a monotonic process-local timestamp and a node ID. Provides strong in-process monotonicity via a logical counter spanning the clock sequence and timestamp fields. Time component order is not suitable for direct binary sorting.
- Version 3 (Name-Based, MD5): Generated by hashing a namespace UUID and a name using MD5.
- Version 4 (Random): Generated from cryptographically secure random numbers. Most common version when sortability is not needed.
- Version 5 (Name-Based, SHA-1): Generated by hashing a namespace UUID and a name using SHA-1. Preferred over V3.
- Version 6 (Reordered Timestamp, Random): Sortable timestamp-ordered UUID. Ordering is based solely on the timestamp (strict in-process monotonicity via atomic CAS). Trailing fields are filled with
crypto/randby default; an explicitly configured node ID is respected if set. - Version 7 (Unix Epoch Timestamp, Random): Combines a 48-bit millisecond timestamp and 12-bit sub-millisecond fraction with 64 bits of random data. Timestamp-ordered but not strictly monotonic within the same fractional bucket. Recommended for new applications needing time-sortable, collision-resistant IDs.
- Version 8 (Custom/Experimental): Allows custom data layout, defined by RFC 9562 for experimental or vendor-specific use.
This package is licensed under the Boost Software License 1.0. See the LICENSE file for details.