Table of contents
Part of nurago, a collection of independent Go packages for backend services.
import "github.com/tecnickcom/nurago/pkg/random"
Package random provides utility functions for generating random bytes, numeric identifiers, UID/UUID values, hexadecimal/base36 IDs, and configurable random strings.
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.
The non-failing helpers (Rnd.RandUint32, Rnd.RandUint64 and Rnd.UUIDv7,
plus Rnd.RandHex64, Rnd.RandString64, Rnd.UID64 and Rnd.UID128, which
are built on them) fall back to math/rand/v2 if the reader fails, so that their
signatures can stay error-free. Rnd.RandomBytes and Rnd.RandString never
fall back: they return the reader’s error.
With the default crypto/rand.Reader the fallback is unreachable, because its
Read cannot return an error. It is reachable for a caller-supplied reader, and
it is silent by default: the entropy source is swapped without an error and
without a signal. Register WithFallbackHook to observe it. The fallback draws
from Go’s OS-seeded ChaCha8 global source, so the output is not predictable, but
it is no longer the source the caller configured.
A reader that never makes progress (one that keeps returning zero bytes with a
nil error, or whose bytes are never usable) is not retried forever: the helpers
give up and report ErrReaderNoProgress rather than hanging.
What It Provides
Rnd.RandomBytesfor raw random byte slices.Rnd.RandUint32andRnd.RandUint64for random integers.Rnd.RandHex64for fixed-length 16-char hexadecimal IDs.Rnd.RandString64for compact base-36 IDs.Rnd.RandStringfor random strings of length n using a configurable byte-to-character map.Rnd.UID64for time-aware 64-bit unique identifiers (TUID64) withTUID64.HexandTUID64.Stringformats.Rnd.UID128for time+random 128-bit unique identifiers (TUID128) withTUID128.HexandTUID128.Stringformats.Rnd.UUIDv7for RFC 9562 UUID version 7 values (UUID).UUID.Format,TUID64.FormatandTUID128.Formatto write the textual form into a caller-owned buffer.
Character Map Customization
Rnd.RandString uses a default map containing digits, uppercase/lowercase
letters, and symbols. You can override it with WithByteToCharMap.
The map is a map of bytes, not of runes: each output position is filled with one byte drawn from it. Entries must therefore be single-byte (ASCII) values.
- Multi-byte UTF-8 runes are not supported. A map containing them is not rejected, but its runes are split into their constituent bytes, which are then drawn independently, so the result is almost always invalid UTF-8.
- Empty map input restores the default map.
- Maps longer than 256 bytes are truncated to 256.
- A single-entry map yields a constant string with no entropy.
Performance
Generation cost is dominated by two largely irreducible operations: reading the
wall clock and reading entropy from the configured reader. Each call makes one
small heap allocation for the random bytes, which is structural: the read goes
through an io.Reader, so the buffer cannot stay on the stack. Rnd.UUIDv7
draws only the 8 random bytes it needs for the 62-bit rand_b field rather than
a full 16, so it reads half the entropy a naive v7 construction does.
The generators hold no shared mutable state and take no locks, so a single Rnd
is safe for concurrent use without serializing callers and concurrent throughput
is bounded by the reader. Ordering of values generated within the same
sub-millisecond instant is statistical rather than strictly monotonic, which is
the trade that keeps the path lock-free.
Textual formatting is table-driven through uhex instead of reflection-based formatting: each byte is translated with a single lookup into a 256-entry table, unrolled and branch-free.
- The
Formatmethods write into a caller-owned array and allocate nothing. For the UUID case this is well over an order of magnitude faster thanfmt.Sprintf("%x-%x-%x-%x-%x", ...), and roughly two to three times faster thanencoding/hexplus manual separator insertion. StringandHexlayer a single result allocation on top ofFormat.Bytefills a local array and returns a slice over it, so it allocates only when the result escapes the caller.
These are relative characteristics, not guarantees; absolute numbers depend on
the hardware, the compiler, and the configured reader. The package ships
benchmarks (run with go test -bench=.) so the figures can be reproduced on the
target platform.
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)
When To Use
- You need UUIDs, hex IDs, or base36 IDs without adding a dedicated dependency.
- Random strings must be drawn from a specified alphabet.
Example
r := random.New(nil)
b, err := r.RandomBytes(4)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%v\n", b)
Full source is in example_random_test.go. More runnable examples are on pkg.go.dev.
Dependencies
This package reaches no external module: it uses only the Go standard library.