Randomness Without the Setup: Luma Gets random.int(), random.float(), and random.bool()
Every general-purpose language needs randomness. Dice rolls, coin flips, shuffling, sampling, test data, procedural generation — random numbers show up constantly in everyday programming. Until today, the only randomness in Luma was crypto.rand(n), which generates cryptographically secure random bytes. Great for tokens and security. Not great when you just want a number between 1 and 6.
Now Luma has a random namespace with three functions that cover the common cases.
What shipped
random.int(min, max)
Returns a random integer in [min, max] — both ends inclusive.
roll: int = random.int(1, 6)
print("Rolled: ${roll}") // 1, 2, 3, 4, 5, or 6Both-inclusive is deliberate. When someone writes random.int(1, 6), they expect 6 to be a possible result. That’s how dice work. That’s how humans think. No mental arithmetic, no off-by-one anxiety.
random.float(min, max)
Returns a random float in [min, max) — min inclusive, max exclusive.
chance: float = random.float(0.0, 1.0)
print("Probability: ${chance}") // 0.0 to 0.999...Half-open for floats is the universal convention. Every language does it this way because returning exactly max for a float is mathematically degenerate — the probability is zero. The range [0.0, 1.0) is a building block for everything from probability distributions to weighted selections.
random.bool()
Returns true or false with equal probability.
heads: bool = random.bool()
if heads {
print("Heads!")
} else {
print("Tails!")
}Could you write random.int(0, 1) == 1 instead? Sure. But random.bool() says what you mean. Code reads better when boolean logic uses boolean types.
No setup required
There’s no seed call. No initialization. No import. You just use it:
roll: int = random.int(1, 6)Luma’s random module uses Go’s math/rand/v2 under the hood, which has been auto-seeded since Go 1.22. Every program run produces a different sequence automatically. This is the right default — in 99% of cases, developers want unpredictable randomness without thinking about seeds.
When to use random vs. crypto.rand
Two modules, two purposes:
| Need | Use | Why |
|---|---|---|
| Dice roll, coin flip | random.int(1, 6) |
Fast, convenient, typed |
| Game logic, simulations | random.float(0.0, 1.0) |
Standard probability range |
| Session tokens, API keys | crypto.rand(32) |
Cryptographically secure |
| Encryption keys, salts | crypto.rand(16) |
OS-level entropy source |
random.* is for everyday programming. crypto.rand() is for security. Don’t use crypto where you need speed and simplicity. Don’t use random where you need unpredictability against an adversary.
What you can build
Dice roller
(1..=5).walk(i) -> {
print("Roll ${i}: ${random.int(1, 6)}")
}Five dice rolls. Clean loop, no ceremony.
Random password
chars: str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
password: str = ""
(1..=16).walk(i) -> {
idx: int = random.int(0, chars.len() - 1)
password = password + chars[idx]
}
print("Password: ${password}")For casual passwords and test data. For real credentials, use crypto.rand() instead.
Weighted decisions
fn weighted_flip(probability: float) -> bool {
return random.float(0.0, 1.0) < probability
}
// 70% chance of rain
if weighted_flip(0.7) {
print("Bring an umbrella")
}random.float(0.0, 1.0) combined with a threshold — the universal pattern for weighted randomness.
Pick a random element
fn pick(items: [str]) -> str {
i: int = random.int(0, items.len() - 1)
return items[i]
}
colors: [str] = ["red", "green", "blue", "yellow"]
print("Picked: ${pick(colors)}")Design decisions
Three functions, not thirty. random.int(), random.float(), random.bool(). These cover the vast majority of use cases. We didn’t add random.choice(), random.shuffle(), random.gaussian(), or any of the other convenience functions some languages offer. Those can be composed from the primitives. Start minimal, expand based on real demand.
Namespace, not keyword. random is not a reserved word. It’s a compile-time namespace, same as crypto, json, env. If you declare random: int = 5 in your code, the compiler won’t intercept it — it respects your variable name. Namespaces are dispatched only when there’s no conflicting declaration.
Feature-flagged as always. A program that never uses random.* gets no random imports, no random functions in the binary. The math/rand/v2 import is only included when the compiler sees a random.* call.
Panics on invalid ranges. random.int(6, 1) panics with a clear message. We could silently swap the arguments, but that hides bugs. If min > max, something is wrong in your logic, and you should know about it immediately.
What’s next
The random module is small by design. If patterns emerge — if developers consistently build the same helpers on top of these three functions — we’ll consider promoting them. Shuffle, weighted choice, and normal distributions are all candidates. But only when there’s real pull, not speculative push.
For now: three functions, zero setup, and randomness that just works.