random

package
v1.148.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package random provides utility functions for generating random bytes, numeric identifiers, UID/UUID values, hexadecimal/base36 IDs, and configurable random strings.

Problem

Application code frequently needs random values for IDs, tokens, test fixtures, or temporary credentials. Repeatedly wiring secure randomness, string encoding, and character-set mapping at call sites is error-prone and inconsistent.

This package centralizes those patterns in one small API.

Randomness Source

By default, New uses crypto/rand.Reader, which is suitable for security-sensitive randomness. A custom io.Reader can be supplied directly to New for testing or specialized environments.

For Rnd.RandUint32 and Rnd.RandUint64, if secure random byte generation fails, the implementation falls back to math/rand/v2 to keep call sites non-failing. This makes those helpers resilient but less suitable for strict cryptographic guarantees when fallback occurs.

What It Provides

Character Map Customization

Rnd.RandString uses a default map containing digits, uppercase/lowercase letters, and symbols. You can override it with WithByteToCharMap.

  • Empty map input restores the default map.
  • Maps longer than 256 bytes are truncated to 256.

Usage

r := random.New(nil) // default: crypto/rand.Reader

id := r.RandHex64()
short := r.RandString64()
_ = id
_ = short

pwd, err := r.RandString(24)
if err != nil {
    return err
}
_ = pwd

uid64 := r.UID64()
uid128 := r.UID128()
uuid := r.UUIDv7()
_ = uid64.Hex()
_ = uid128.String()
_ = uuid.String()

alphaNum := []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
r2 := random.New(nil, random.WithByteToCharMap(alphaNum))
_, _ = r2.RandString(16)

This package is ideal for Go services that need convenient, reusable random value generation with sensible secure defaults and explicit customization.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNegativeLength = errors.New("random: negative length")

ErrNegativeLength is returned when a negative length is requested from a size-taking helper such as Rnd.RandomBytes or Rnd.RandString.

Functions

This section is empty.

Types

type Option

type Option func(c *Rnd)

Option is the interface that allows to set client options.

func WithByteToCharMap

func WithByteToCharMap(cm []byte) Option

WithByteToCharMap customizes the byte-to-character mapping for RandString(). Empty maps restore the default; maps > 256 bytes are truncated to 256.

type Rnd

type Rnd struct {
	// contains filtered or unexported fields
}

Rnd defines the random number generator.

func New

func New(r io.Reader, opts ...Option) *Rnd

New constructs a random generator using the specified reader (or crypto/rand.Reader if nil) with optional customization.

func (*Rnd) RandHex64

func (r *Rnd) RandHex64() string

RandHex64 returns a random 64-bit value as a 16-character hexadecimal string.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)

	h := r.RandHex64()

	fmt.Println(h)
}

func (*Rnd) RandString

func (r *Rnd) RandString(n int) (string, error)

RandString returns an n-character random string using the configured character map, suitable for passwords.

Characters are selected with rejection sampling so each character map entry is drawn uniformly, even when 256 is not a multiple of the map length. A negative n returns ErrNegativeLength instead of panicking; n == 0 returns an empty string.

Example
package main

import (
	"fmt"
	"log"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)

	s, err := r.RandString(16)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(s)
}

func (*Rnd) RandString64

func (r *Rnd) RandString64() string

RandString64 returns a random 64-bit value as a variable-length base-36 string.

It encodes a single value, so it is unambiguous, but it is variable-length; callers that need a fixed-width hexadecimal form should use Rnd.RandHex64.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)

	h := r.RandString64()

	fmt.Println(h)
}

func (*Rnd) RandUint32

func (r *Rnd) RandUint32() uint32

RandUint32 returns a random 32-bit value, falling back to math/rand/v2 if crypto/rand fails.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)

	n := r.RandUint32()

	fmt.Println(n)
}

func (*Rnd) RandUint64

func (r *Rnd) RandUint64() uint64

RandUint64 returns a random 64-bit value, falling back to math/rand/v2 if crypto/rand fails.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)

	n := r.RandUint64()

	fmt.Println(n)
}

func (*Rnd) RandomBytes

func (r *Rnd) RandomBytes(n int) ([]byte, error)

RandomBytes generates a slice of n random bytes, returning error if the reader fails.

It uses io.ReadFull so the destination is always fully populated; a custom io.Reader that returns a short read with a nil error is treated as an error rather than silently truncating the randomness. A negative n returns ErrNegativeLength instead of panicking; n == 0 returns an empty slice.

Example
package main

import (
	"fmt"
	"log"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)

	b, err := r.RandomBytes(4)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%v\n", b)
}

func (*Rnd) UID64

func (r *Rnd) UID64() TUID64

UID64 generates a 64-bit unique identifier with upper 32 bits as a time-decade offset and lower 32 bits random.

Because only the lower 32 bits are random and the upper 32 bits are shared by all identifiers generated within the same second, uniqueness relies on a single 32-bit random draw per second: birthday collisions become likely at roughly 2^16 (~77k) identifiers generated within the same second. For high-throughput or collision-critical use, prefer Rnd.UID128 (64 random bits) or Rnd.UUIDv7 (~74 random bits).

The second offset is measured from the start of the current decade, so it stays well within 32 bits; note that it resets at each decade boundary, which makes the value time-ordered only within a decade (see TUID64.Hex).

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)
	v := r.UID64()

	fmt.Println(v)
}

func (*Rnd) UID128

func (r *Rnd) UID128() TUID128

UID128 generates a 128-bit unique identifier with high 64 bits from current time and low 64 bits random.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)
	v := r.UID128()

	fmt.Println(v)
}

func (*Rnd) UUIDv7

func (r *Rnd) UUIDv7() UUID

UUIDv7 generates a 128-bit RFC 9562 UUID version 7 (time-based with random).

UUIDs generated by this function are suitable for distributed, unique identification with the following properties:

  • Timestamp component (48 bits): millisecond-precision Unix epoch time
  • Random component (80 bits): pseudorandom data for uniqueness and collision avoidance
  • Sortable: UUIDs generated at later times have higher values (useful for database indexing)
  • Cryptographically strong randomness is not guaranteed; use only for non-cryptographic purposes

The function returns the UUID in binary form. Use String() or Byte() to get the standard human-readable format.

Performance:

  • Each call makes a single small heap allocation for the random bytes. This is structural: randomness is read through the configurable io.Reader, so the compiler cannot keep the buffer on the stack. The 16-byte UUID itself is stack-allocated and returned by value.
  • Per-call cost is dominated by two largely irreducible operations: reading the wall clock (time.Now) and reading random bytes from the reader (crypto/rand by default). It draws only the 8 random bytes it needs (the 62-bit rand_b field plus the variant octet), rather than generating a full 16 random bytes and overwriting the timestamp octets as a naive v7 construction does, so it reads half the entropy; the bit-packing and version/variant masking are negligible by comparison.
  • The function holds no shared mutable state and takes no locks of its own, so a single Rnd is safe for concurrent use and callers are not serialized; concurrent throughput is bounded by the configured reader rather than by this function. Ordering of values generated within the same sub-millisecond instant is statistical, not strictly monotonic; that is the trade that keeps the path lock-free.
  • For the textual form, UUID.Format builds the canonical string with table-driven lookups instead of reflection-based formatting such as fmt.Sprintf: it writes into a caller-owned buffer without allocating, whereas UUID.Byte and UUID.String each allocate once for their result. Prefer Format in hot paths; see UUID.Format for the comparison with the standard-library encoders.

These are qualitative characteristics, not guarantees; absolute costs depend on hardware, the Go compiler, and the configured reader. Run the package benchmarks (go test -bench=.) to measure on a target platform.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)
	v := r.UUIDv7()

	fmt.Println(v)
}

type TUID64

type TUID64 uint64

TUID64 is a time-ordered 64-bit identifier: the upper 32 bits are a decade-relative second offset and the lower 32 bits are random.

func (TUID64) Byte

func (u TUID64) Byte() []byte

Byte returns the UID64 as its 16-character hexadecimal form in a byte slice.

Each call allocates a new [16]byte array. Use TUID64.Hex for a string, or TUID64.Format to write into a pre-allocated buffer without allocating.

func (TUID64) Format

func (u TUID64) Format(dst *[16]byte)

Format writes the UID64 as its 16-character hexadecimal form into dst.

Format writes directly into the caller-provided array and allocates nothing, so prefer it in performance-critical code. For a string use TUID64.Hex; for a byte slice use TUID64.Byte. Values are time-ordered within a decade (see Rnd.UID64).

func (TUID64) Hex

func (u TUID64) Hex() string

Hex returns the UID64 as a 16-character hexadecimal string.

Values are time-ordered within a decade; ordering is discontinuous across decade boundaries because the underlying second offset resets (see Rnd.UID64).

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)
	v := r.UID64().Hex()

	fmt.Println(v)
}

func (TUID64) String

func (u TUID64) String() string

String returns the UID64 as a variable-length base-36 string.

Unlike TUID128.String, this encodes a single value and is therefore unambiguous and round-trippable. It is, however, variable-length; callers that need a fixed-width representation should use TUID64.Hex.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)
	v := r.UID64().String()

	fmt.Println(v)
}

type TUID128

type TUID128 struct {
	// contains filtered or unexported fields
}

TUID128 is a time-ordered 128-bit identifier: the high 64 bits (t) are the generation time in Unix nanoseconds and the low 64 bits (r) are random.

func (TUID128) Byte

func (u TUID128) Byte() []byte

Byte returns the UID128 as its 32-character hexadecimal form in a byte slice.

Each call allocates a new [32]byte array. Use TUID128.Hex for a string, or TUID128.Format to write into a pre-allocated buffer without allocating.

func (TUID128) Format

func (u TUID128) Format(dst *[32]byte)

Format writes the UID128 as its 32-character hexadecimal form into dst.

The output is the high 64 bits (time) followed by the low 64 bits (random), each as 16 lowercase hexadecimal digits, preserving time-order.

Format writes directly into the caller-provided array and allocates nothing, so prefer it in performance-critical code. For a string use TUID128.Hex; for a byte slice use TUID128.Byte.

func (TUID128) Hex

func (u TUID128) Hex() string

Hex returns the UID128 as a 32-character hexadecimal string, preserving time-order.

It encodes both halves into a single stack buffer with the table-driven uhex helpers and converts once, so it costs a single allocation for the returned string. Use TUID128.Format for an allocation-free path.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)
	v := r.UID128().Hex()

	fmt.Println(v)
}

func (TUID128) String

func (u TUID128) String() string

String returns the UID128 as a variable-length base-36 string (concatenated high and low parts).

This form is intended for display only and is NOT guaranteed to be unique or round-trippable: because both parts are variable-length and concatenated without a separator, two distinct TUID128 values can map to the same string. Callers that need a unique, collision-free, round-trippable representation should use TUID128.Hex, which is fixed-width.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)
	v := r.UID128().String()

	fmt.Println(v)
}

type UUID

type UUID [16]byte

UUID is a 128-bit (16-byte) Universally Unique Identifier (UUID) as defined in RFC 9562 (https://datatracker.ietf.org/doc/html/rfc9562).

UUIDs are commonly used to uniquely identify resources, records, or entities in distributed systems without requiring centralized coordination. This type represents the binary form; use String() or Byte() to get the standard textual representation in the format "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".

func (UUID) Byte

func (u UUID) Byte() []byte

Byte returns the UUID as a 36-byte slice in the standard textual format.

The output is: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" (36 characters) where each character is a lowercase hexadecimal digit.

Each call to Byte() allocates a new [36]byte array. Use String() if you need a Go string instead of a byte slice, or Format() for maximum performance if you already have a pre-allocated buffer.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)
	v := r.UUIDv7().Byte()

	fmt.Println(string(v))
}

func (UUID) Format

func (u UUID) Format(dst *[36]byte)

Format writes the UUID in standard textual format to dst.

The output format is: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" (36 characters) where each 'x' is a lowercase hexadecimal digit.

Use this method when you need to write the UUID directly into a pre-allocated buffer for performance-critical code. For general-purpose use, prefer String() which returns a Go string, or Byte() which returns a slice.

Performance: Format builds the canonical text with the table-driven uhex encoders instead of reflection-based formatting. Each byte is translated through a 256-entry lookup table and written with a single 16-bit store, so the routine is fully unrolled, branch-free, independent of the UUID value, and writes into the caller's array without allocating. Against the usual standard-library ways of producing the same string this is markedly cheaper: versus fmt.Sprintf("%x-%x-%x-%x-%x", …) it is roughly an order of magnitude faster and avoids the reflection-driven allocations, and versus encoding/hex plus manual separator insertion it is faster and needs no intermediate buffer. UUID.String and UUID.Byte layer a single result allocation on top of this.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)
	u := r.UUIDv7()

	var b [36]byte

	u.Format(&b)

	fmt.Println(string(b[:]))
}

func (UUID) String

func (u UUID) String() string

String returns the UUID as a standard 36-character string.

The output is: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" where each character is a lowercase hexadecimal digit.

This is the preferred method for most use cases (logging, JSON marshaling, display). Use Byte() if you need a byte slice, or Format() if working with pre-allocated buffers in performance-critical code.

Example
package main

import (
	"fmt"

	"github.com/tecnickcom/nurago/pkg/random"
)

func main() {
	r := random.New(nil)
	v := r.UUIDv7().String()

	fmt.Println(v)
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL