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 ¶
- 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.
- 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 ¶
- 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 = 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 ¶
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 ¶
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-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)
}
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 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)
}
Output:
func (*Rnd) RandUint64 ¶
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)
}
Output:
func (*Rnd) RandomBytes ¶
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)
}
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 (~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)
}
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
- 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)
}
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.
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 ¶
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.
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 ¶
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).
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 ¶
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))
}
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 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[:]))
}
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: