std/random¶
A small, reproducible pseudo-random generator. Rng is a value type holding
the generator state; its methods draw the next value and mutate that state in
place.
import std/random
fun main() {
let rng = Rng.new(42)
print(rng.gen_range(1, 7)) // a dice roll in [1, 7)
}
Importing¶
std/random adds a struct Rng and its impl Rng block, so a bare import
brings the constructors (Rng.new, Rng.from_entropy) and every method below
into scope. Import the whole module (not a selective { ... } list).
This generator is not cryptographically secure. Do not use it for keys, tokens, or anything where predictability is a risk.
Reproducibility¶
The generator is splitmix64: the same seed always yields the same sequence of
draws. That determinism is the point of seeded construction, and it makes
std/random a good fit for reproducible tests and simulations.
Rng.from_entropy() instead seeds from a runtime source (a high-resolution
timestamp mixed with the process id), so its stream is not reproducible across
runs. Reach for it when you want a fresh, unpredictable sequence.
import std/random
fun main() {
let a = Rng.new(7)
let b = Rng.new(7)
print(a.next_int() == b.next_int()) // true: same seed, same draw
}
Constructing¶
Rng.new(seed: Int) -> Rng¶
A generator seeded with seed. Deterministic: two generators built from the
same seed produce identical sequences.
Rng.from_entropy() -> Rng¶
A generator seeded from a runtime time/pid source. Non-reproducible across runs.
Drawing numbers¶
next_int(self) -> Int¶
The next raw 64-bit draw. Values span the full Int range and may be negative.
import std/random
fun main() {
let rng = Rng.new(1)
print(rng.next_int()) // some i64 value
print(rng.next_int()) // the next one in the sequence
}
gen_range(self, lo: Int, hi: Int) -> Int¶
A value in the half-open interval [lo, hi): lo is possible, hi is not.
import std/random
fun main() {
let rng = Rng.new(99)
print(rng.gen_range(0, 6)) // one of 0, 1, 2, 3, 4, 5
}
The result is a non-negative draw reduced modulo (hi - lo). The modulo
introduces a small bias when the range does not evenly divide the draw space,
but for typical small ranges the bias is negligible. If lo >= hi the function
returns lo.
next_float(self) -> Float¶
A Float in [0.0, 1.0). It takes the top 53 bits of a draw and scales by
2^-53 (the full f64 mantissa width), so every representable multiple of 2^-53
in the interval is reachable.
gen_range_float(self, lo: Float, hi: Float) -> Float¶
A Float in the half-open interval [lo, hi). Built from next_float
scaled into the range.
import std/random
fun main() {
let rng = Rng.new(42)
print(rng.gen_range_float(0.0, 10.0)) // e.g. 7.41...
}
next_bool(self) -> Bool¶
true or false from the low bit of a draw, a fair coin flip.
Working with lists¶
choice<T>(self, xs: List<T>) -> Option<T>¶
A random element of xs, or None when the list is empty. Because the result
is an Option<T>, unwrap it with a match (or your preferred Option helper)
before use.
import std/random
fun main() {
let rng = Rng.new(12)
let colors = ["red", "green", "blue"]
match rng.choice(colors) {
Some(c) -> print(c),
None -> print("empty"),
}
}
shuffle<T>(self, xs: List<T>)¶
Shuffles xs in place using Fisher-Yates. It returns nothing; the list passed
in is reordered.
import std/random
fun main() {
let rng = Rng.new(2024)
let deck = [1, 2, 3, 4, 5]
rng.shuffle(deck)
for card in deck {
print(card) // some permutation of 1, 2, 3, 4, 5
}
}
sample<T>(self, xs: List<T>, n: Int) -> List<T>¶
Draw n elements from xs without replacement, in random order. If n
exceeds the list length, every element is returned (shuffled). The source
list is left unchanged.
import std/random
fun main() {
let rng = Rng.new(42)
let xs = ["a", "b", "c", "d", "e"]
for p in rng.sample(xs, 3) {
print(p) // three distinct elements, random order
}
}
weighted_choice<T>(self, xs: List<T>, weights: List<Int>) -> Option<T>¶
Choose an element of xs with probability proportional to the matching entry
of weights. Returns None when the lists are empty or the total weight is
not positive.
import std/random
fun main() {
let rng = Rng.new(42)
let items = ["red", "green", "blue"]
let weights = [1, 1, 8]
match rng.weighted_choice(items, weights) {
Some(c) -> print(c), // "blue" most of the time
None -> print("empty"),
}
}
Worked example: a reproducible sampler¶
A seeded generator makes a test deterministic: pick the same seed and the output never drifts between runs.
import std/random
fun main() {
let rng = Rng.new(7)
let words = ["alpha", "beta", "gamma", "delta"]
// Draw three random words.
let i = 0
while i < 3 {
match rng.choice(words) {
Some(w) -> print(w),
None -> print("(none)"),
}
i = i + 1
}
// Shuffle the deck in place and report a coin flip.
rng.shuffle(words)
for w in words {
print(w)
}
print(rng.next_bool())
}
Run it twice with the same seed and you get byte-for-byte identical output.
Change the 7 to Rng.from_entropy() and each run differs.
See also¶
- std/math for the numeric functions you will often pair with random draws.
- The language reference for
Option,match, and generics.