# Pseudo RNGs

**URL:** <https://ziggit.dev/t/pseudo-rngs/14440>\
**Category:** Help\
**Tags:** standard-library\
**Created:** [February 26, 2026, 4:12am UTC](https://ziggit.dev/t/pseudo-rngs/14440 "2026-02-26T04:12:36Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![jmafc](https://ziggit.dev/user_avatar/ziggit.dev/jmafc/32/7763_2.png) [@jmafc](https://ziggit.dev/u/jmafc)\
**Post date:** [February 26, 2026, 4:12am UTC](https://ziggit.dev/t/pseudo-rngs/14440/1 "2026-02-26T04:12:36Z")

</div>

I’m trying a very simple “roll a die” example. In C stdlib, this would entail using`srand()`and `rand()`. In modern C++, there are more sophisticated features, such as `std::random_device` which provides an `entropy()`method, `std::default_random_engine`as well as various other engines, and `std::uniform_int_distribution`as well as different variants.

My search for Zig examples turned up this very simple one: [Zig Cookbook](https://cookbook.ziglang.cc/06-01-rand/), but it doesn’t cover seeding. I found `std.random.int` in the std library docs but it doesn’t seem to explain the “underlying magic”. I also found this [more extensive example](https://nofmal.github.io/zig-with-example/rng/) but it’s against an earlier release (I’m using 0.15.2). After fixing it, it looks like this:

```zig
const time: u128 = @bitCast(std.time.nanoTimestamp());
const seed: u64 = @truncate(time);
var prng = std.Random.Xoshiro256.init(seed);
const random_number = prng.next();
const die: u8 = (random_number % 6) + 1;

```

BTW, I had to guess on how to do the above because if you click on `DefaultPrng`it takes you to `Xoshiro256`and there are no descriptions in there.

Zig doesn’t like that last line, giving the error `error: expected type ‘u8’, found ‘u64’`with the note `unsigned 8-bit int cannot represent all possible unsigned 64-bit values`. What bothers me is that, yes, `random_number`is `u64`, but by the mathematical definition of modulus division, any number modulo 6 can only give an integer between 0 and 5, which definitely fits in a `u8`, so why should I have to add an additional cast? Am I missing something?

Side note: My original title was “PRNGs” but that was not acceptable because it’s less than 10 characters. I’m left wondering if that is a local limitation or a software “feature” and what purpose does it serve?

---

<div class="post-metadata">

**Author:** ![jmctagger](https://ziggit.dev/letter_avatar_proxy/v4/letter/j/8dc957/32.png) [@jmctagger](https://ziggit.dev/u/jmctagger)\
**Post date:** [February 26, 2026, 4:52am UTC](https://ziggit.dev/t/pseudo-rngs/14440/2 "2026-02-26T04:52:26Z")

</div>

[This recent thread](https://ziggit.dev/t/creating-and-modifying-an-array-of-random-floats/14425/14) covers some non-cryptographic rng stuff, including using the (juicy) init.io.random for the “easiest” way forward the likes of die-rolling. You might find some help there.

Yes, it looks like next() returns a u64; even though you %6 that value, you could add a million to it, instead of just 1. Obviously you don’t, and you might think the compiler should see that the result can fit into 8 bits, but, alas, you’ll have to `@truncate` that result. Since you’re sure that won’t modify your value, you can do it safely. This expresses your intent clearly, rather than leaving it as “implied”, anyway.

---

<div class="post-metadata">

**Author:** ![jmafc](https://ziggit.dev/user_avatar/ziggit.dev/jmafc/32/7763_2.png) [@jmafc](https://ziggit.dev/u/jmafc)\
**Post date:** [February 26, 2026, 5:19am UTC](https://ziggit.dev/t/pseudo-rngs/14440/3 "2026-02-26T05:19:01Z")

</div>

I didn’t mention it before, but the error is given even if nothing is added, i.e.,

```zig
const die: u8 = random_number % 6;

```

still gives the error with the same error and note. It’s not a big deal, but it seems the compiler would be better if, when processing the % operator it would take into account the size of the divisor rather than invoking “peer type resolution”. In any case, I used an `@intCast`rather than a `@truncate`since the former looks more appropritate.

---

<div class="post-metadata">

**Author:** ![pzittlau](https://ziggit.dev/letter_avatar_proxy/v4/letter/p/ecae2f/32.png) [@pzittlau](https://ziggit.dev/u/pzittlau)\
**Post date:** [February 26, 2026, 6:22am UTC](https://ziggit.dev/t/pseudo-rngs/14440/4 "2026-02-26T06:22:39Z")

</div>

This works:

```zig
const std = @import("std");

pub fn main() !void {
    var prng = std.Random.DefaultPrng.init(@intCast(std.time.nanoTimestamp()));
    const random = prng.random();

    const die = random.intRangeAtMost(u8, 1, 6);
    std.debug.print("{}\n", .{die});
}

```

The `prng.random()` call is important here, since it gets you a bunch of nice functions. See [here](https://ziglang.org/documentation/0.15.2/std/#std.Random)

---

<div class="post-metadata">

**Author:** ![invlpg](https://ziggit.dev/letter_avatar_proxy/v4/letter/i/13edae/32.png) [@invlpg](https://ziggit.dev/u/invlpg)\
**Post date:** [February 26, 2026, 9:31am UTC](https://ziggit.dev/t/pseudo-rngs/14440/5 "2026-02-26T09:31:15Z")

</div>

Note that `std.time.nanoTimestamp` will be removed in 0.16.

On master, I’d probably write it like this:

```zig
pub fn main(init: std.process.Init) !void {
    var prng: std.Random.DefaultPrng = prng: {
        const now: std.Io.Timestamp = .now(init.io, .real);
        // FIXME: this @intCast will only be safe until year 1.255 * 10^12 
        break :prng .init(@intCast(now.toNanoseconds()));
    };
    const random = prng.random();

    const die = random.intRangeAtMost(u8, 1, 6);
    std.debug.print("{}\n", .{die});
}

```

Or alternatively, if I need my dice rolls to be cryptographically secure:

```zig
pub fn main(init: std.process.Init) !void {
    var csprng: std.Random.DefaultCsprng = prng: {
        var seed: [std.Random.DefaultCsprng.secret_seed_length]u8 = undefined;
        init.io.random(&seed); // alternatively: init.io.randomSecure depending on requirements
        break :prng .init(seed);
    };
    const random = csprng.random();

    const die = random.intRangeAtMost(u8, 1, 6);
    std.debug.print("{}\n", .{die});
}

```

EDIT: Ignore my second suggestion above. You can just use `Io`’s CSPRNG directly, rather than instantiating another:

```zig
pub fn main(init: std.process.Init) !void {
    var csprng: std.Random.IoSource = .{ .io = init.io };
    const random = csprng.interface();

    const die = random.intRangeAtMost(u8, 1, 6);
    std.debug.print("{}\n", .{die});
}
```

---

<div class="post-metadata">

**Author:** ![jmafc](https://ziggit.dev/user_avatar/ziggit.dev/jmafc/32/7763_2.png) [@jmafc](https://ziggit.dev/u/jmafc)\
**Post date:** [February 26, 2026, 2:00pm UTC](https://ziggit.dev/t/pseudo-rngs/14440/6 "2026-02-26T14:00:39Z")

</div>

Maybe I’m still unfamiliar with Zig syntax or its documentation, but I don’t see a `random()` function or method in the [page you mention](https://ziglang.org/documentation/0.15.2/std/#std.Random).

---

<div class="post-metadata">

**Author:** ![invlpg](https://ziggit.dev/letter_avatar_proxy/v4/letter/i/13edae/32.png) [@invlpg](https://ziggit.dev/u/invlpg)\
**Post date:** [February 26, 2026, 2:26pm UTC](https://ziggit.dev/t/pseudo-rngs/14440/7 "2026-02-26T14:26:10Z")

</div>

The `Random` type is the “interface”/vtable. @pzittlau’s post instantiates a concrete implementation of that “interface” of type `DefaultPrng` as a variable called `prng`. The `.random` method is on `DefaultPrng`. I put “interface” in quotes, because Zig doesn’t really have that concept at a language level. Instead, for dynamic dispatch, a struct full of function pointers (a manually created vtable) is often used (usually with convenience wrapper methods).

The `.random()` call returns the vtable (of type `std.Random`) associated with the concrete instantiation of `DefaultPrng` (the variable `prng`).

---

<div class="post-metadata">

**Author:** ![jmafc](https://ziggit.dev/user_avatar/ziggit.dev/jmafc/32/7763_2.png) [@jmafc](https://ziggit.dev/u/jmafc)\
**Post date:** [February 26, 2026, 4:14pm UTC](https://ziggit.dev/t/pseudo-rngs/14440/8 "2026-02-26T16:14:01Z")

</div>

I understand what you describe. However, it seems it would be good if this “arcane” information were included somewhere in the language documentation.
