Random
Pseudo-random numbers backed by xorshift64\ — fast, with a full 64-bit period. It is not* cryptographically secure, but it is exactly what you want for shuffling, sampling, randomized tests, and reproducible simulations.
Seed deterministically with `Rng::with_seed` for runs you can replay, or from the monotonic clock with `Rng::from_clock` for "random enough".
Import
import stdlib::random::*;
Public surface at a glance
The module exposes exactly one type and eight methods — three constructors/ destructor and five draw helpers. Everything below is built on next_i64.
| Item | Signature | Purpose |
|---|---|---|
Rng |
pub struct Rng { state: i64 } |
The generator (one i64 of state). |
with_seed |
pub fn with_seed(seed: i64) -> *Rng |
Construct from a known seed (reproducible). |
from_clock |
pub fn from_clock() -> *Rng |
Construct from now_ns() ("random enough"). |
free |
pub fn free(self: *Rng) |
Release the generator. |
next_i64 |
pub fn next_i64(self: *Rng) -> i64 |
Raw 64-bit value (wraps; may be negative). |
next_int |
pub fn next_int(self: *Rng) -> i32 |
Non-negative i32 in [0, 2^31 - 1]. |
range |
pub fn range(self: *Rng, lo: i32, hi: i32) -> i32 |
Uniform i32 in [lo, hi). |
next_f64 |
pub fn next_f64(self: *Rng) -> f64 |
Uniform f64 in [0.0, 1.0). |
next_bool |
pub fn next_bool(self: *Rng) -> bool |
true with probability ~0.5. |
The Rng generator
pub struct Rng { state: i64 }
Rng holds a single i64 of internal state. It is heap-allocated by its constructors and must be released with `free` — pair it with defer for leak-free use.
Construction
| Function | Signature | Description |
|---|---|---|
with_seed |
pub fn with_seed(seed: i64) -> *Rng |
Build an Rng from a known seed; same seed always yields the same stream. |
from_clock |
pub fn from_clock() -> *Rng |
Build an Rng seeded from the monotonic clock (now_ns()). |
free |
pub fn free(self: *Rng) |
Release the generator. |
Rng::with_seed
pub fn with_seed(seed: i64) -> *Rng
Construct an Rng with a known starting state. The same seed always produces the same stream, making this the right choice for tests and reproducible simulations.
import stdlib::random::*;
fn main() -> i32 {
let r: *Rng = Rng::with_seed(42);
defer r.free();
println!("first draw", r.next_int()); // same value on every run
return 0;
}
Rng::from_clock
pub fn from_clock() -> *Rng
Construct an Rng seeded from the monotonic clock. Internally this is just Rng::with_seed(now_ns()).
import stdlib::random::*;
fn main() -> i32 {
let r: *Rng = Rng::from_clock();
defer r.free();
for i in 0..5 { println!("d6", r.range(1, 7)); } // five dice rolls
return 0;
}
free
pub fn free(self: *Rng)
Frees the generator's heap allocation. Because the constructors hand back a raw *Rng, you are responsible for the lifetime — defer r.free(); right after construction is the idiomatic pattern.
Drawing values
Every draw advances the internal state in place. The full catalog:
| Method | Signature | Range / result |
|---|---|---|
next_i64 |
pub fn next_i64(self: *Rng) -> i64 |
Raw 64-bit value (wraps); full entropy. |
next_int |
pub fn next_int(self: *Rng) -> i32 |
Non-negative i32 in [0, 2^31 - 1]. |
range |
pub fn range(self: *Rng, lo: i32, hi: i32) -> i32 |
Uniform i32 in [lo, hi). |
next_f64 |
pub fn next_f64(self: *Rng) -> f64 |
Uniform f64 in [0.0, 1.0). |
next_bool |
pub fn next_bool(self: *Rng) -> bool |
true with probability ~0.5. |
All five together, from one seeded generator:
import stdlib::random::*;
fn main() -> i32 {
let r: *Rng = Rng::with_seed(42);
defer r.free();
let raw: i64 = r.next_i64(); // full 64-bit, may be negative
let n: i32 = r.next_int(); // 0 .. 2^31-1
let d6: i32 = r.range(1, 7); // 1..=6
let p: f64 = r.next_f64(); // [0.0, 1.0)
let flip: bool = r.next_bool(); // ~50/50
println!("raw", raw);
println!("int", n);
println!("d6", d6);
if flip { println!("heads"); } else { println!("tails"); }
if p < 0.5 { println!("low half"); }
return 0;
}
next_i64
pub fn next_i64(self: *Rng) -> i64
Next raw 64-bit value: one xorshift step followed by a multiply with an odd constant. The result wraps and may be negative. Use this when you want all 64 bits of entropy (hashing, jitter); for bounded integers prefer `range`. Every other draw helper is a thin wrapper over this.
let raw: i64 = r.next_i64();
next_int
pub fn next_int(self: *Rng) -> i32
Next non-negative i32. The value is masked to 31 bits so the result is always positive after the i64 -> i32 cast — keeping all 32 bits would let the high bit make it negative. Range: [0, 2^31 - 1].
let n: i32 = r.next_int(); // 0 .. 2^31-1
range
pub fn range(self: *Rng, lo: i32, hi: i32) -> i32
Uniform integer in the half-open interval [lo, hi). Returns lo when the range is empty (hi <= lo), so it never traps on a degenerate span.
let dice: i32 = r.range(1, 7); // d6 -> 1..=6
let pct: i32 = r.range(0, 100); // 0..=99
let same: i32 = r.range(5, 5); // empty range -> returns 5
next_f64
pub fn next_f64(self: *Rng) -> f64
Uniform float in [0.0, 1.0). Built from the upper 53 bits of a raw draw so the result fits exactly in an f64 mantissa with no boundary rounding error. Ideal for probability thresholds and Bernoulli trials.
let p: f64 = r.next_f64();
if p < 0.1 { /* runs ~10% of the time */ }
next_bool
pub fn next_bool(self: *Rng) -> bool
Coin flip — returns true with probability ~0.5 (it tests the low bit of a raw draw).
if r.next_bool() { println!("heads"); } else { println!("tails"); }
Reproducibility
Two generators built from the same seed produce identical sequences, which is what makes seeded runs replayable — record the seed, and you can replay the exact run later.
import stdlib::random::*;
fn main() -> i32 {
let a: *Rng = Rng::with_seed(12345);
defer a.free();
let b: *Rng = Rng::with_seed(12345);
defer b.free();
let mut matched: bool = true;
for i in 0..10 {
if a.next_i64() != b.next_i64() { matched = false; }
}
if matched { println!("identical streams"); }
return 0;
}
Distribution sanity check — a Bernoulli trial built on next_f64:
import stdlib::random::*;
fn main() -> i32 {
// Count ~250 hits out of 1000 trials at p = 0.25.
let r: *Rng = Rng::with_seed(0); // 0 is rewritten to a non-zero state
defer r.free();
let mut hits: i32 = 0;
for i in 0..1000 {
if r.next_f64() < 0.25 { hits = hits + 1; }
}
println!("approx 250", hits);
return 0;
}
Recipes
This module ships only seeding and the five draw helpers; the common collection operations are short to build yourself.
Picking a random element (choice)
Index a vector with range(0, len):
import stdlib::random::*;
fn pick(r: *Rng, v: *Vector<i32>) -> i32 {
let i: i32 = r.range(0, v.len() as i32);
return v.get(i as usize);
}
fn main() -> i32 {
let nums: *Vector<i32> = Vector::new();
defer nums.free();
push_all!(nums, 10, 20, 30, 40, 50);
let r: *Rng = Rng::with_seed(99);
defer r.free();
for i in 0..3 { println!("pick", pick(r, nums)); }
return 0;
}
Shuffling (Fisher–Yates)
Walk from the last index down, swapping each element with a random earlier (or equal) one. Note the inclusive upper bound r.range(0, i + 1).
import stdlib::random::*;
fn shuffle(r: *Rng, v: *Vector<i32>) {
let n: i32 = v.len() as i32;
let mut i: i32 = n - 1;
while i > 0 {
let j: i32 = r.range(0, i + 1); // 0..=i
let tmp: i32 = v.get(i as usize);
v.set(i as usize, v.get(j as usize));
v.set(j as usize, tmp);
i = i - 1;
}
}
fn main() -> i32 {
let deck: *Vector<i32> = Vector::new();
defer deck.free();
for k in 0..8 { deck.push(k); }
let r: *Rng = Rng::with_seed(2024);
defer r.free();
shuffle(r, deck);
for k in 0..deck.len() { println!("card", deck.get(k)); }
return 0;
}
Random bytes
There is no bytes helper; draw each byte with range(0, 256):
import stdlib::random::*;
fn fill_bytes(r: *Rng, out: *Vector<i32>, count: i32) {
let mut i: i32 = 0;
while i < count {
out.push(r.range(0, 256)); // a byte 0..=255
i = i + 1;
}
}
fn main() -> i32 {
let buf: *Vector<i32> = Vector::new();
defer buf.free();
let r: *Rng = Rng::with_seed(7);
defer r.free();
fill_bytes(r, buf, 16);
println!("len", buf.len());
println!("first", buf.get(0));
return 0;
}
See also
- `time` —
now_ns()is whatfrom_clockseeds from. - The
vectorreference —Vector::new,push,get,set,lenused in
the recipes above.