Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

std.random

import std.random provides deterministic pseudo-random generators that the caller creates, seeds, owns, and passes explicitly, with one error type, RandomError.

The module is checked Plenty code with no native primitives. It never reads the operating system, the clock, or any process-wide state: there is no global generator and no automatic seeding. It is available on the supported x86-64 Linux GNU target.

A generator’s output is predictable from its seed, and from a few outputs. Do not use it for keys, tokens, passwords, or anything an adversary must not guess; use std.secrets. Collection hashing is unrelated to both modules.

import std.random
from std.random import RandomError

def main() -> Result[(), AllocError | IoError | RandomError]:
    mut rng = std.random.seeded(2026u64)
    print(rng.between(1, 7)?, rng.between(1, 7)?, rng.next_bool())?
    mut deck = ["A", "B", "C", "D", "E"]?
    rng.shuffle(&mut deck)
    print(deck)?
    print(rng.sample_indices(10, 3)?)?
    mut replay = std.random.seeded(2026u64)
    print(replay.between(1, 7)?)?
    Ok(())
6 5 True
["D", "A", "E", "C", "B"]
[9, 7, 1]
6

Generators

FunctionResultContract
seeded(seed: u64)RngExpand the seed into a full state; every seed is valid
from_state(a: u64, b: u64, c: u64, d: u64)Result[Rng, RandomError]Restore a state taken from state(); all zeros is ZeroState

Rng has private fields, so these two functions are the only way to make one. To seed from the operating system, pass std.secrets.random_u64() to seeded and record the seed if the run must be repeatable.

Methods that draw take self: &mut Rng.

MethodResultContract
next_u64()u64One step; all 64 bits are uniform
next_u32()u32The high half of one step
next_bool()boolThe top bit of one step
next_f64()f64A multiple of 2^-53 in [0, 1)
below(bound: u64)Result[u64, RandomError]Uniform in [0, bound); zero is EmptyRange
between(low: i64, high: i64)Result[i64, RandomError]Uniform in [low, high); low >= high is EmptyRange
index(count: i64)Result[i64, RandomError]Uniform index into count elements; zero is EmptyRange, negative traps
shuffle[T](items: &mut list[T])()Uniform permutation in place
sample_indices(count: i64, amount: i64)Result[list[i64], AllocError]amount distinct indices below count, in selection order; a negative count or amount outside 0..=count traps
fill(target: &mut Bytes, start: i64, stop: i64)()Overwrite target[start:stop]
state()(u64, u64, u64, u64)The current state words; takes &Rng

Ranges are half-open and never clamp. between covers any ordered pair of i64 bounds, including i64.MIN to i64.MAX. fill follows the range rule. An argument that no call could accept, such as a negative count, is a caller bug and traps with a message naming the method; an empty range is EmptyRange because it can come from data, such as an empty list.

below, between, and index are unbiased: values of a step that would make some results more likely are rejected and another step is drawn. A draw takes fewer than two steps on average for every bound.

shuffle is Fisher-Yates. It exchanges elements with swap, so it works for any element type and never copies or drops an element. To pick k of n elements, call sample_indices(n, k) and index the collection.

A rejected request (EmptyRange or a failed allocation) does not advance the generator. Lists of fewer than two elements and empty fill ranges also consume no steps.

Algorithm and stability

Rng is xoshiro256** 1.0: 256 bits of state and a period of 2^256 - 1. seeded fills the state with four successive SplitMix64 outputs, as the algorithm’s authors recommend.

The sequences are version-stable. For a given seed or state, every method above returns the same values in every Plenty release, on every thread and run. The derived methods are part of that contract:

MethodDefinition
next_u32next_u64() >> 32
next_boolBit 63 of next_u64()
next_f64(next_u64() >> 11) * 2^-53
below(bound)Draw next_u64() until the value is at least 2^64 mod bound; return it modulo bound
between(low, high)low + below(high - low), computed in u64
shuffleFor last from len - 1 down to 1: swap last with below(last + 1)
sample_indicesOver the list 0..count, for position from 0 below amount: swap position with position + below(count - position); return the first amount
fillEach step supplies eight bytes, least significant first; a final partial group takes the low bytes of one more step

A different algorithm would be a new constructor, not a change to seeded.

Errors

pub enum RandomError:
    EmptyRange
    ZeroState
VariantMeaning
EmptyRangeThe requested range holds no value
ZeroStatefrom_state was given the all-zero state, which never advances

Bounded draws validate NonZero[u64] before rejection sampling. Generator mixing still uses explicit wrapping operations; list/sample/fill cursors use documented bounds-proven arithmetic opt-outs. Neither changes the version-stable draw sequence, failure-state guarantees or public error types.

Ownership and threads

A generator is an ordinary owned value of four u64 words stored inline. Drawing needs an exclusive loan, so two parts of a program cannot interleave draws from one generator by accident; give each its own, seeded differently. The module holds no global state, passes the ordinary worker-effect checks, and a generator can be moved into a worker thread. There is no built-in way to derive independent streams from one seed: use distinct seeds.

Costs

OperationAllocatesTime
seeded, from_state, stateNothingConstant
next_u64, next_u32, next_bool, next_f64, below, between, indexNothingConstant; expected constant for the bounded draws
shuffleNothingLinear, one bounded draw per element
fillNothingLinear, one checked Bytes.set per byte
sample_indices(count, amount)One list of count indicesLinear in count

sample_indices is proportional to count, not amount. Throughput has not been measured on an idle machine.

Limits

  • One algorithm. No jump or split functions for parallel streams.
  • Normal, exponential and floating uniform distributions are provided by std.stat, using an explicitly supplied Rng. Weighted choice is not provided.
  • No f32 draws and no inclusive ranges.
  • sample_indices returns indices; there is no element-sampling or reservoir form.

Acknowledgements

  • Uses the xoshiro256** generator by David Blackman and Sebastiano Vigna, seeded with SplitMix64 by Guy Steele, Doug Lea and Christine Flood.