Why does calling std.posix.getrandom throw error?

file: Fruit.zig

pub fn init_random(rows: u32, columns: u32) Self {
    var this = init(0, 0);
    if (this.rand == null) {
        var __rand = std.rand.DefaultPrng.init(blk: {
            var seed: u64 = undefined;
            std.posix.getrandom(std.mem.asBytes(&seed)) catch |err| {
                std.debug.print("Got error: {}", .{err});
            };
            break :blk seed;
        });
        this.rand = __rand.random();
    }
    const rand_x = this.rand.intRangeLessThan(usize, 0, columns);
    const rand_y = this.rand.intRangeLessThan(usize, 0, rows);

    this.x = rand_x;
    this.y = rand_y;
    std.debug.print("{any}\n", .{this});
    return this;
}

Writing this code about i get the following error:

/home/ledrake/.local/share/zig-linux-x86_64-0.13.0/lib/std/posix.zig:581:43: error: comptime call of extern function
                const rc = std.c.getrandom(buf.ptr, buf.len, 0);
                           ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
src/Fruit.zig:22:32: note: called from here
            std.posix.getrandom(std.mem.asBytes(&seed)) catch {};
            ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
src/Grid.zig:21:33: note: called from here
fruit: Fruit = Fruit.init_random(Self.rows, Self.columns),
               ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    Self: src/Grid.zig:8:14
    Grid: src/Grid.zig:21:45
    remaining reference traces hidden; use '-freference-trace' to see all reference traces
error: the following command failed with 1 compilation errors:
/home/ledrake/.local/share/zig-linux-x86_64-0.13.0/zig build-exe /home/ledrake/files/zig/snake_clone_zig/.zig-cache/o/c05909e159268da4caa80e86e9fd89a3/libraylib.a -ODebug -I /home/ledrake/files/zig/snake_clone_zig/.zig-cache/o/8edfb742c363db90f6c4bd0d23f41e18 --dep config --dep raylib --dep raygui -Mroot=/home/ledrake/files/zig/snake_clone_zig/src/main.zig -Mconfig=/home/ledrake/files/zig/snake_clone_zig/.zig-cache/c/f4f0e9ac1319d5626a0e4d187455046a/options.zig -ODebug -Mraylib=/home/ledrake/.cache/zig/p/12202f8c415153088be8df39a51e0a4c9d402afd403422a0dcc9afdd417e437a6fdb/lib/raylib.zig -ODebug --dep raylib-zig=raylib -Mraygui=/home/ledrake/.cache/zig/p/12202f8c415153088be8df39a51e0a4c9d402afd403422a0dcc9afdd417e437a6fdb/lib/raygui.zig -lGL -lX11 -lc --cache-dir /home/ledrake/files/zig/snake_clone_zig/.zig-cache --global-cache-dir /home/ledrake/.cache/zig --name snake_clone --listen=-
Build Summary: 4/9 steps succeeded; 1 failed (disable with --summary none)
run transitive failure
└─ run snake_clone transitive failure
   ├─ zig build-exe snake_clone Debug native 1 errors
   └─ install transitive failure
      └─ install snake_clone transitive failure
         └─ zig build-exe snake_clone Debug native (+3 more reused dependencies)
error: the following build command failed with exit code 1:
/home/ledrake/files/zig/snake_clone_zig/.zig-cache/o/99df77fd6fb92261294e5d94736265af/build /home/ledrake/.local/share/zig-linux-x86_64-0.13.0/zig /home/ledrake/files/zig/snake_clone_zig /home/ledrake/files/zig/snake_clone_zig/.zig-cache /home/ledrake/.cache/zig --seed 0x2fb23b69 -Z99df11b0e0ecfff9 run

And i don’t really understand what it means with error: comptime call of extern function

When i run it in its own file like this, on its own project
file: main.zig

const std = @import("std");

pub fn main() !void {
    var __rand = std.rand.DefaultPrng.init(blk: {
        var seed: u64 = undefined;
        std.posix.getrandom(std.mem.asBytes(&seed)) catch |err| {
            std.debug.print("Got error: {}", .{err});
        };
        break :blk seed;
    });
    const rand = __rand.random();
    const rand_x = rand.intRangeLessThan(usize, 0, 10);
    const rand_y = rand.intRangeLessThan(usize, 0, 9);
    std.debug.print("x:{},y:{}\n", .{ rand_x, rand_y });
}

I get the correct output:
x:1,y:8

Note: I don’t know if it’s important but I am running things on WSL

I am going to answer my own problem. I am new to zig and junior in programming. The anwser was that i was calling this function as a field default value in another struct, which i guess might be comptime only. Anyone with a better explaination is free to correct me!

2 Likes

Can you show the definition of Fruit, the capitalized Self.rows, makes me suspect that maybe you are misusing container level variables instead of fields?

This error message basically tells us that the random generator you end up invoking at comptime can’t be executed because it involves an extern function (this was news to me but it makes sense because Zig wants comptime to be executed during cross compilation and having dependencies on the host would make that very difficult, because things could become dependent on host differences)

Default Field Values:

Each struct field may have an expression indicating the default field value. Such expressions are executed at comptime, and allow the field to be omitted in a struct literal expression

Because you are calling a function in the default field expression, that function is executed at comptime, that function then continues to use random number generator that ends up using the c-library std.c.getrandom function.

This uses then std.c.private.getrandom which is a extern c function.

I think the easiest for you might be to instead just start initializing things at runtime.

For example you could set your fruit field like so: fruit: ?Fruit = null,
and then at runtime check if fruit is null and if it is call Fruit.init_random(Self.rows, Self.columns) to set its value and initialize it.

Another good option might be to just don’t use default values here and instead initialize the surrounding struct with its fruit field manually, then you need to create the instance of the struct within a function and assign it to another field or variable so that it happens at runtime, instead of assigning it to some initialization expression of a global that gets executed at comptime.

It also might be possible (but complicated and probably not pretty to use) to create a random number generator that works at comptime, but I think in most cases this isn’t actually what you want to do and it is easier and more practical to have a random number generator that works at runtime.

1 Like

file: Fruit.zig

const std = @import("std");
const rl = @import("raylib");

const Self = @This();

x: usize = 0,
y: usize = 0,
color: rl.Color = rl.Color.orange,

rand: ?std.Random = null,

pub fn init(x: usize, y: usize) Self {
    return Self{ .x = x, .y = y };
}

pub fn init_random(rows: u32, columns: u32) Self {
    // TODO: make it so the fruit doesn't spawn in the snake
    var this = init(0, 0);
    if (this.rand == null) {
        var __rand = std.rand.DefaultPrng.init(blk: {
            var seed: u64 = undefined;
            std.posix.getrandom(std.mem.asBytes(&seed)) catch |err| {
                std.debug.print("Got error: {}", .{err});
            };
            break :blk seed;
        });
        this.rand = __rand.random();
    }
    const rand_x = this.rand.?.intRangeLessThan(usize, 0, columns);
    const rand_y = this.rand.?.intRangeLessThan(usize, 0, rows);

    this.x = rand_x;
    this.y = rand_y;
    return this;
}

file: Grid.zig

const std = @import("std");
const arr_utils = @import("arrayUtils.zig");
const rl = @import("raylib");

const Game = @import("Game.zig");
const Fruit = @import("Fruit.zig");

const Self = @This();

const columns: u32 = 15;
const rows: u32 = 8;

game_ctx: *Game,

cell_width: u32 = 80,
cell_padding: u32 = 10,
cell_size: rl.Vector2 = undefined,
...

I think a better way would be to construct the random number generator in your main function create the random interface to it and then pass that interface to all things that need to generate random numbers, that way you should naturally end up writing your code in such a way that the random number generator is used at runtime.

So I think it would be better if you pass the rand: std.Random as a parameter to init_random instead of constructing it there.

I also think you may be overusing default values, if multiple fields have values that relate to another it is better to instead create a specific instance of the struct that has these values set together, with the upcoming 0.14.0 zig also has a convenient syntax to use these values.

Hopefully this helps, let me know if anything doesn’t make sense to you
and welcome to Ziggit @NikosGour!

1 Like

I will eventually get to that implementation , i just don’t want to prematurly optimize my code structure. When i see i need rand elsewhere i will do exactly that

I like the idea of a default instance, thanks! Also thanks for the warm welcome!

1 Like

Hello @NikosGour

@Sze advise about constructing the random number generator in your main function is the best.

Another way to get the random interface is to use static variables (single instance of the value like global variables):

const std = @import("std");

fn random() std.Random {
    const S = struct {
        var prng: ?std.rand.DefaultPrng = null;
    };
    if (S.prng) |*rand| {
        return rand.random();
    }
    const seed = std.crypto.random.int(u64);
    S.prng = std.rand.DefaultPrng.init(seed);
    return S.prng.?.random();
}

test {
    const columns = 40;
    const rows = 25;
    const rand_column = random().intRangeLessThan(usize, 0, columns);
    const rand_row = random().intRangeLessThan(usize, 0, rows);
    try std.testing.expect(rand_column >= 0 and rand_column < columns);
    try std.testing.expect(rand_row >= 0 and rand_row < rows);
}

welcome to ziggit :slight_smile:

3 Likes

I’m not quite sure the reason why you had problem but if you want in std.testing.random_seed is probably better and more idiomatic than what you are doing, because reading from undefined memory feels like a code smell, even if I get your intent behind it.

from here it seems that it always sets the seed to 0. I don’t think that’s correct

Yes but you can pass something to the build system I don’t remember, you can also do something like this in your build.zig

    const seed = b.option(usize, "random_seed", "random_seed") orelse 0;
    const option = b.addOptions();
    option.addOption(usize, "seed", seed);

    const exe = b.addExecutable(.{
        .name = "tt",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    exe.root_module.addOptions("my_options", option);
    b.installArtifact(exe);

and int your main.zig

const std = @import("std");
const opt = @import("my_options");
const random_seed = opt.seed;

In fn random() will the S struct will only be defined once?

There no instances of S (named after static). struct S is used to hold the single instance of the static var prng (the member variables–that have an instance for each struct instance–are not prefixed by var). Each time you call random the S.prng is the same.

1 Like