Documentation
¶
Overview ¶
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.RandomBytes for raw random byte slices.
- Rnd.RandUint32 and Rnd.RandUint64 for random integers.
- Rnd.RandHex64 for fixed-length 16-char hexadecimal IDs.
- Rnd.RandString64 for compact base-36 IDs.
- Rnd.RandString for random strings of length n using a configurable byte-to-character map.
- Rnd.UID64 for time-aware 64-bit unique identifiers (TUID64) with TUID64.Hex and TUID64.String formats.
- Rnd.UID128 for time+random 128-bit unique identifiers (TUID128) with TUID128.Hex and TUID128.String formats.
- Rnd.UUIDv7 for RFC 9562 UUID version 7 values (UUID).
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.
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)
Index ¶
- Variables
- type Option
- type Rnd
- func (r *Rnd) RandHex64() string
- func (r *Rnd) RandString(n int) (string, error)
- func (r *Rnd) RandString64() string
- func (r *Rnd) RandUint32() uint32
- func (r *Rnd) RandUint64() uint64
- func (r *Rnd) RandomBytes(n int) ([]byte, error)
- func (r *Rnd) UID64() TUID64
- func (r *Rnd) UID128() TUID128
- func (r *Rnd) UUIDv7() UUID
- type TUID64
- type TUID128
- type UUID
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNegativeLength is returned when a negative length is requested from a // size-taking helper such as [Rnd.RandomBytes] or [Rnd.RandString]. ErrNegativeLength = errors.New("random: negative length") // ErrReaderNoProgress is returned when the configured [io.Reader] cannot be // made to yield usable entropy: either it keeps returning zero bytes with a // nil error, or (in [Rnd.RandString]) every byte it produces is rejected by // the rejection sampler. Both are legal [io.Reader] behaviors that would // otherwise loop forever, so they are reported instead of retried without // bound. It is unreachable with the default [crypto/rand.Reader]. ErrReaderNoProgress = errors.New("random: reader is not producing usable entropy") // ErrInvalidCharMap is returned by [Rnd.RandString] when the character map is // empty or longer than 256 bytes. [New] and [WithByteToCharMap] both normalize // the map, so this is reachable only through the unusable zero value of [Rnd]. ErrInvalidCharMap = errors.New("random: character map must hold 1 to 256 bytes") )
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 ¶
WithByteToCharMap customizes the byte-to-character mapping for RandString(). Empty maps restore the default; maps > 256 bytes are truncated to 256.
The map is copied, so the caller keeps ownership of the slice it passes in and may reuse, mutate, or zero it afterwards without affecting the generator.
The map holds bytes, not runes, and Rnd.RandString draws one byte per output position, so entries must be single-byte (ASCII) values. A map containing multi-byte UTF-8 runes is not rejected, but each rune is split into its constituent bytes and those bytes are then drawn independently of one another, so the generated strings are almost always invalid UTF-8. Truncation at 256 bytes can likewise split a trailing rune.
func WithFallbackHook ¶ added in v1.150.0
func WithFallbackHook(fn func()) Option
WithFallbackHook registers fn to be called whenever the configured io.Reader fails and a non-failing helper (Rnd.RandUint32, Rnd.RandUint64, Rnd.UUIDv7, and everything built on them) silently substitutes math/rand/v2 for it.
The substituted source is Go's OS-seeded ChaCha8 global generator, so the output is not predictable; the reason to observe the event is that the entropy source is no longer the one the caller configured, which matters for auditing and for alerting on a failing HSM- or KMS-backed reader. With the default crypto/rand.Reader the hook can never fire, because its Read cannot fail.
fn is called synchronously on the generating goroutine, so it must be fast and must not call back into the same Rnd. It may be called concurrently from several goroutines and must therefore be safe for concurrent use.
type Rnd ¶
type Rnd struct {
// contains filtered or unexported fields
}
Rnd defines the random number generator.
The zero value is not usable: construct one with New, which installs the default reader and character map. Methods on a zero-value Rnd return ErrInvalidCharMap or panic on the nil reader.
A single Rnd is safe for concurrent use: it is immutable after construction and every method allocates its own buffers. WithByteToCharMap copies the caller's character map, so a caller retaining the original slice cannot mutate the generator through it.
func New ¶
New constructs a random generator using the specified reader (or crypto/rand.Reader if nil) with optional customization.
func (*Rnd) RandHex64 ¶
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)
}
Output:
func (*Rnd) RandString ¶
RandString returns an n-byte random string using the configured character map, suitable for passwords.
Entries 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.
Selection is byte-oriented: each of the n output positions holds one byte from the map, so n is a length in bytes and the map must contain single-byte (ASCII) entries. Multi-byte UTF-8 runes in a custom map (see WithByteToCharMap) are not supported and are not rejected: their bytes are drawn independently of one another, so the returned string is almost always invalid UTF-8. With the default map, n bytes is n characters.
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)
}
Output:
func (*Rnd) RandString64 ¶
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)
}
Output:
func (*Rnd) RandUint32 ¶
RandUint32 returns a random 32-bit value, falling back to math/rand/v2 if the reader fails.
The fallback is silent by default; register WithFallbackHook to observe it.
Example ¶
package main
import (
"fmt"
"github.com/tecnickcom/nurago/pkg/random"
)
func main() {
r := random.New(nil)
n := r.RandUint32()
fmt.Println(n)
}
Output:
func (*Rnd) RandUint64 ¶
RandUint64 returns a random 64-bit value, falling back to math/rand/v2 if the reader fails.
The fallback is silent by default; register WithFallbackHook to observe it.
Example ¶
package main
import (
"fmt"
"github.com/tecnickcom/nurago/pkg/random"
)
func main() {
r := random.New(nil)
n := r.RandUint64()
fmt.Println(n)
}
Output:
func (*Rnd) RandomBytes ¶
RandomBytes generates a slice of n random bytes, returning error if the reader fails.
The destination is always fully populated: reads are repeated until it is full, so a custom io.Reader that returns short reads is retried rather than silently truncating the randomness, and a reader that ends early yields io.ErrUnexpectedEOF. A reader that never makes progress yields ErrReaderNoProgress rather than looping forever. A negative n returns ErrNegativeLength instead of panicking; n == 0 returns an empty slice and does not touch the reader.
Unlike Rnd.RandUint32 and Rnd.RandUint64, this never falls back to math/rand/v2: a reader failure is surfaced to the caller.
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)
}
Output:
func (*Rnd) UID64 ¶
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 (~65k) identifiers generated within the same second, about a 39% chance at 65k, and 50% at ~77k. For high-throughput or collision-critical use, prefer Rnd.UID128 (64 random bits) or Rnd.UUIDv7 (62 random bits; its other 12 non-timestamp bits are clock-derived, not random).
The second offset is measured from the start of the current decade, so it stays well within 32 bits; 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)
}
Output:
func (*Rnd) UID128 ¶
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)
}
Output:
func (*Rnd) UUIDv7 ¶
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
- Sub-millisecond clock precision (12 bits, rand_a): the nanosecond remainder of the current millisecond, scaled into [0, 4095]. This is RFC 9562 §6.2 Method 3 ("Replace Leftmost Random Bits with Increased Clock Precision"), and it is what makes values generated within the same millisecond sortable. These bits are clock-derived, not random.
- Random component (62 bits, rand_b): pseudorandom data for uniqueness and collision avoidance. 62 is the total random bit count of the value: the version (4 bits) and variant (2 bits) are fixed, and rand_a is clock-derived.
- 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, which spans octets 8-15; the 2 variant bits are overwritten with constants), 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. UUID.String always allocates once for its result; UUID.Byte allocates its [36]byte only when the returned slice escapes the caller, and is free when it does not. 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)
}
Output:
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 ¶
Byte returns the UID64 as its 16-character hexadecimal form in a byte slice.
Byte fills a local [16]byte array and returns a slice over it, so whether it allocates depends on escape analysis at the call site: if the returned slice does not escape the caller (it is read and discarded, or only its bytes are consumed), the array stays on the stack and no allocation occurs; if it escapes (stored, returned, sent on a channel, or passed to a function the compiler cannot inline), the array is heap-allocated. Use TUID64.Hex for a string, or TUID64.Format to write into a pre-allocated buffer and never allocate regardless of escape analysis.
func (TUID64) Format ¶
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 ¶
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)
}
Output:
func (TUID64) 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)
}
Output:
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 ¶
Byte returns the UID128 as its 32-character hexadecimal form in a byte slice.
Byte fills a local [32]byte array and returns a slice over it, so whether it allocates depends on escape analysis at the call site: if the returned slice does not escape the caller (it is read and discarded, or only its bytes are consumed), the array stays on the stack and no allocation occurs; if it escapes (stored, returned, sent on a channel, or passed to a function the compiler cannot inline), the array is heap-allocated. Use TUID128.Hex for a string, or TUID128.Format to write into a pre-allocated buffer and never allocate regardless of escape analysis.
func (TUID128) Format ¶
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 ¶
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)
}
Output:
func (TUID128) 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)
}
Output:
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).
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 ¶
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.
Byte fills a local [36]byte array and returns a slice over it, so whether it allocates depends on escape analysis at the call site: if the returned slice does not escape the caller (it is read and discarded, or only its bytes are consumed), the array stays on the stack and no allocation occurs; if it escapes (stored, returned, sent on a channel, or passed to a function the compiler cannot inline), the array is heap-allocated. Use String() if you need a Go string instead of a byte slice, or Format() to write into a pre-allocated buffer and never allocate regardless of escape analysis.
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))
}
Output:
func (UUID) Format ¶
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 cheaper: versus fmt.Sprintf("%x-%x-%x-%x-%x", …) it is well over an order of magnitude faster (~40x on amd64) and avoids the reflection-driven allocations, and versus encoding/hex plus manual separator insertion it is roughly 2-3x faster, though both write into a caller-owned buffer without allocating. 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[:]))
}
Output:
func (UUID) 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)
}
Output: