ADR-0027: Random Number Intrinsics
Status
Implemented (2026-01-02)
Summary
Add @random_u32() and @random_u64() intrinsics that generate cryptographically-secure random numbers using platform syscalls, enabling interactive examples like guessing games without requiring a complex PRNG implementation or global state management.
Context
Interactive programs like guessing games require randomness. The Rust Book's guessing game tutorial is a canonical example for learning:
use Rng;
Gruel currently has no randomness capability. Adding it requires choosing between several approaches:
- Intrinsic functions - Simple, stateless, syscall-based
- Built-in PRNG type - Stateful, requires passing state or global variables
- External library - Requires module system and package management (not yet available)
For Gruel's current stage of development, intrinsics are the best fit:
- No generics yet (needed for
Random<T>) - No module system (needed for external libraries)
- Educational use cases don't require determinism or PRNG state
- Fits existing intrinsic pattern (
@dbg,@read_line, etc.)
Decision
Add two new intrinsics:
@random_u32()
Signature:
@random_u32() -> u32
Behavior:
- Returns a random unsigned 32-bit integer
- Takes no arguments
- Non-deterministic - each call produces a different value
- Uses platform entropy source (cryptographically secure)
Example:
fn main() -> i32 {
let secret: u32 = (@random_u32() % 100) + 1; // 1-100
@dbg("Guess the number between 1 and 100!");
let mut guesses = 0;
loop {
let input = @read_line();
let guess = @parse_u32(input);
guesses = guesses + 1;
if guess < secret {
@dbg("Too low!");
} else if guess > secret {
@dbg("Too high!");
} else {
@dbg("You got it!");
break;
}
}
@intCast(guesses)
}
@random_u64()
Signature:
@random_u64() -> u64
Behavior:
- Identical to
@random_u32()but returns 64-bit values - Provided for consistency with other intrinsics that have 32/64-bit variants
Runtime Implementation
Platform-specific entropy sources:
| Platform | Method | Syscall/API |
|---|---|---|
| Linux x86-64 | getrandom() | Syscall #318 |
| Linux aarch64 | getrandom() | Syscall #278 |
| macOS aarch64 | getentropy() | libSystem function |
Error handling:
- If entropy source is unavailable: runtime panic with "randomness unavailable"
- If syscall fails: runtime panic with "random number generation failed"
Implementation:
// In gruel-runtime
pub extern "C"
pub extern "C"
Compiler Pipeline Changes
- Lexer: No change (already handles
@prefix) - Parser: Add
RandomU32andRandomU64toIntrinsicKindenum - Sema:
- Validate zero arguments
- Type as
u32oru64return type - Gate behind
PreviewFeature::Random
- CFG: Lower to
Intrinsic::RandomU32orIntrinsic::RandomU64 - Codegen: Generate
call __gruel_random_u32orcall __gruel_random_u64 - Linker: Link runtime functions (already automatic)
Specification
Add to docs/spec/src/04-expressions/13-intrinsics.md:
Implementation Phases
Phase 1: Spec and tests - gruel-ddko
- Add spec documentation for
@random_u32and@random_u64 - Add preview-gated spec tests (non-deterministic, so test compilation only)
- Add
RandomtoPreviewFeatureenum
- Add spec documentation for
Phase 2: Parser and Sema - gruel-ub3z
- Add
RandomU32andRandomU64to intrinsic parsing - Add type checking (returns u32/u64, takes no args)
- Add preview feature gate check
- Add CFG lowering for random intrinsics
- Add
Phase 3: Runtime implementation - gruel-5852
- Implement
__gruel_random_u32for x86-64 Linux - Implement
__gruel_random_u64for x86-64 Linux - Implement for aarch64 macOS (using getentropy from libSystem)
- Implement for aarch64 Linux
- Handle error cases (no entropy source)
- Implement
Phase 4: Codegen - gruel-2h42
- Add call generation for x86-64 backend
- Add call generation for aarch64 backend
- Integration testing with example programs (spec tests)
Consequences
Positive
- Enables interactive educational examples (guessing games, dice rolling, simulations)
- Simple mental model - just call the function, no state management
- Platform-native cryptographic quality randomness (suitable for non-crypto uses)
- No runtime state or initialization required
- Fits existing intrinsic pattern
- Works with Gruel's
no_stdphilosophy
Negative
- Non-deterministic behavior makes testing harder (can't assert specific values)
- Syscall overhead on every call (vs batched PRNG with local state)
- No way to seed for reproducibility (testing, debugging, simulations)
- Limited to unsigned integers (no floating point, no other distributions)
- Requires different syscalls per platform (maintenance burden)
Neutral
- Users must implement their own range helpers (
random_range(min, max)) - Statistical quality depends on platform entropy source
- Not suitable for cryptographic purposes (but neither advertised as such)
Open Questions
None - design is approved.
Future Work
Explicitly out of scope for this ADR:
Seeded PRNG state: Would require either:
- Global mutable state (not idiomatic)
- Passing state through function calls (needs linear types)
- A built-in PRNG type (needs generics for
Rng<T>)
Range helpers:
fn random_range(min, max)best left to user code or future standard libraryOther distributions: Uniform floats, gaussian, etc. require floating point support
Statistical quality tests: Validate platform entropy sources meet quality standards
Deterministic mode:
--deterministicflag that seeds with fixed value for testing
References
- Rust's
randcrate: https://docs.rs/rand/ - Rust Book's guessing game: https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html
- Linux
getrandom()syscall: https://man7.org/linux/man-pages/man2/getrandom.2.html - macOS
getentropy(): https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getentropy.2.html - ADR-0016: Preview Feature Infrastructure
- ADR-0021: Stdin Input (precedent for intrinsics with I/O)