Generate CUIDs in GO

Resources  |  Generate CUIDs in GO

Generating a CUID (Collision-resistant Unique Identifier) in Go can be achieved by following the core principles of CUID generation: timestamp, counter, machine fingerprint, and randomness. Here is an implementation in Go:

Steps to Generate a CUID in Go

  1. Timestamp: Get the current timestamp in milliseconds.
  2. Counter: Use a counter to handle multiple CUIDs generated in a short period.
  3. Fingerprint: Generate a machine-specific fingerprint.
  4. Randomness: Add random characters to further reduce the risk of collisions.
  5. Base36 Encoding: Encode the components using Base36 for readability.

Example Code

package main

import (
    "crypto/md5"
    "encoding/hex"
    "fmt"
    "math/rand"
    "net"
    "os"
    "strings"
    "sync"
    "time"
)

var (
    counter int
    mutex   sync.Mutex
)

const base36Chars = "0123456789abcdefghijklmnopqrstuvwxyz"

func encodeBase36(value int64) string {
    if value == 0 {
        return "0"
    }

    result := ""
    for value > 0 {
        remainder := value % 36
        result = string(base36Chars[remainder]) + result
        value /= 36
    }
    return result
}

func getMachineFingerprint() string {
    hostname, err := os.Hostname()
    if err != nil {
        hostname = "unknown"
    }
    hash := md5.Sum([]byte(hostname))
    return hex.EncodeToString(hash[:])[:4]
}

func getRandomString(length int) string {
    rand.Seed(time.Now().UnixNano())
    result := make([]byte, length)
    for i := 0; i < length; i++ {
        result[i] = base36Chars[rand.Intn(36)]
    }
    return string(result)
}

func generateCUID() string {
    now := time.Now().UnixNano() / int64(time.Millisecond)

    mutex.Lock()
    if now == int64(counter) {
        counter++
    } else {
        counter = 0
    }
    counterValue := counter
    mutex.Unlock()

    timestamp := encodeBase36(now)
    counterStr := encodeBase36(int64(counterValue))
    fingerprint := getMachineFingerprint()
    randomStr := getRandomString(4)

    return fmt.Sprintf("c%s%s%s%s", timestamp, counterStr, fingerprint, randomStr)
}

func main() {
    fmt.Println(generateCUID())
}

Explanation

  1. Base36 Encoding:

    • The encodeBase36 function converts a decimal number to a Base36 encoded string.
  2. Timestamp:

    • now := time.Now().UnixNano() / int64(time.Millisecond) gets the current timestamp in milliseconds.
  3. Counter:

    • A counter is used to handle multiple CUIDs generated within the same millisecond. The counter is protected by a mutex to ensure thread safety.
  4. Machine Fingerprint:

    • getMachineFingerprint generates a fingerprint using the MD5 hash of the machine's hostname.
  5. Random String:

    • getRandomString generates a random string of specified length using Base36 characters.
  6. Combine Components:

    • The generateCUID function combines the timestamp, counter, fingerprint, and random parts to form the final CUID.

Summary

This Go implementation of CUID generation uses available functions and libraries to handle timestamps, hashing, randomness, and thread-safe counters. This approach ensures that the generated CUIDs are unique, readable, and collision-resistant, making them suitable for various applications.