Skip to content

std/random

Pure pseudo-random generation. A random.Generator<T> is an immutable recipe for producing a T; run it against a random.Seed with Generator.step to get back the value paired with the next seed. Same seed in -> same value out, so every draw is reproducible and testable. The only impurity is Seed.from_os, which reads OS entropy for a fresh starting seed.

Import with import std/random.

enum Error {
    InvalidIntRange { from: Int, to: Int }
    InvalidFloatRange { from: Float, to: Float }
    InvalidWeight { weight: Float }
    ZeroWeightTotal
    OsEntropy { reason: String }
}

Errors returned when constructing a generator whose invariants can be checked before any random draw happens, or when OS entropy is unavailable.

fn to_string(e: Error): String

impl Display.to_string

fn inspect(value: Error): String

impl Debug.inspect

type Seed Int

opaque — construction surface is private to its defining module

Opaque PRNG state (a splitmix64 register). Thread it explicitly through Generator.step; never mutated. Seed reproducibly with Seed.from_int, or pull a non-deterministic one with Seed.from_os.

fn from_int(n: Int): Seed

type function on Seed

A reproducible seed from an integer. Same n -> same sequence, forever.

Interactive Tests

seed = Seed.from_int(123)
assert Ok(die) = Generator.int(1, 6)
(roll, _) = Generator.step(die, seed)
assert roll == 2
fn from_os(): Result<Seed, Error>

type function on Seed

A non-deterministic seed from OS entropy. The one impure call on Seed — the entry point when you want real randomness.

fn inspect(value: Seed): String

impl Debug.inspect

struct Generator<T>

opaque — construction surface is private to its defining module

An immutable recipe for producing a T. Build one with a constructor (Generator.int, …), reshape it with map / flat_map, then run it with Generator.step. Constructors whose invariants can be invalid return Result; unwrap them with try or case before composing.

fn step<T>(gen: Generator<T>, seed: Seed): (T, Seed)

type function on Generator

Produce a value and the next seed. The pure core of Generator.

die = try Generator.int(1, 6)
(n, _) = die |> Generator.step(Seed.from_int(42))

Interactive Tests

gen = Generator.constant("heads")
(value, _) = Generator.step(gen, Seed.from_int(1))
assert value == "heads"
fn int(from: Int, to: Int): Result<Generator<Int>, Error>

type function on Generator

Uniform integer in the inclusive range [from, to]. Returns Err when from > to.

Interactive Tests

assert Ok(die) = Generator.int(1, 6)
(roll, _) = Generator.step(die, Seed.from_int(123))
assert roll >= 1 and roll <= 6
assert Err(Error.InvalidIntRange{from: 6, to: 1}) = Generator.int(6, 1)
fn float(from: Float, to: Float): Result<Generator<Float>, Error>

type function on Generator

Uniform float in the half-open range [from, to). Returns Err when from >= to.

Interactive Tests

assert Ok(gen) = Generator.float(1.5, 2.5)
(x, _) = Generator.step(gen, Seed.from_int(123))
assert x >= 1.5 and x < 2.5
assert Err(Error.InvalidFloatRange{from: 2.5, to: 1.5}) = Generator.float(
  2.5,
  1.5,
)
fn bool(): Generator<Bool>

type function on Generator

True or False with equal probability.

Interactive Tests

(value, _) = Generator.step(Generator.bool(), Seed.from_int(123))
assert value == True or value == False
fn constant<T>(value: T): Generator<T>

type function on Generator

Always produces value (consuming no randomness, seed unchanged).

Interactive Tests

(value, _) = Generator.step(Generator.constant("fixed"), Seed.from_int(123))
assert value == "fixed"
fn uniform<T>(first: T, rest: List<T>): Generator<T>

type function on Generator

Uniform choice among first and rest — non-empty by construction.

Generator.uniform("rock", ["paper", "scissors"])

Interactive Tests

(choice, _) = Generator.uniform("rock", ["paper", "scissors"])
|> Generator.step(Seed.from_int(123))
assert choice == "paper" or choice == "rock" or choice == "scissors"
fn weighted<T>(first: (Float, T), rest: List<(Float, T)>): Result<Generator<T>, Error>

type function on Generator

Weighted choice; each option carries a relative weight. Non-empty. Weights must be non-negative, and at least one weight must be positive; they need not sum to 1.

Interactive Tests

assert Ok(gen) = Generator.weighted((1.0, "always"), [])
(choice, _) = Generator.step(gen, Seed.from_int(123))
assert choice == "always"
assert Err(Error.ZeroWeightTotal) = Generator.weighted((0.0, "never"), [])
fn map<T, U>(gen: Generator<T>, f: (T) -> U): Generator<U>

type function on Generator

Transform the produced value.

Interactive Tests

gen = Generator.constant(21) |> Generator.map(|n| n * 2)
(value, _) = Generator.step(gen, Seed.from_int(123))
assert value == 42
fn flat_map<T, U>(gen: Generator<T>, f: (T) -> Generator<U>): Generator<U>

type function on Generator

Sequence a dependent generator — the bind operation.

gen = try Generator.int(1, 6)
gen |> Generator.flat_map(|n|
  Generator.list(Generator.bool(), n)
)

Interactive Tests

gen =
  Generator.constant(3)
  |> Generator.flat_map(|n| Generator.constant(n * 10))
(value, _) = Generator.step(gen, Seed.from_int(123))
assert value == 30
fn list<T>(gen: Generator<T>, length: Int): Generator<List<T>>

type function on Generator

A list of length independent draws from gen. A non-positive length produces an empty list.

Interactive Tests

gen = Generator.constant("ha") |> Generator.list(3)
(values, _) = Generator.step(gen, Seed.from_int(123))
assert values == ["ha", "ha", "ha"]
fn inspect<T>(value: Generator<T>): String where T: Debug

impl Debug.inspect