Why does `std.Thread.spawn` need a comptime function?

I’m trying to find way to spawn a thread with a runtime-known function pointer, but the callback has the comptime keyword on it. Why is this the case? AFAIK pthreads don’t have this requirement, and I’m on Linux (no libc, so i know it’s not the pthread interface, but it should be similar, no?), so I am a little unsure what I’m missing. Below is an example to demonstrate my meaning.

I think it’s because .spawn() requires a function body, instead of a pointer, but why is that?

const std = @import("std");

fn some_fn() void {
    std.debug.print("Hello from thread\n", .{});
}

pub fn main() !void {
    // non-comptime known pointer
    // if I change this to a const,
    // this compiles and runs sucessfully
    var some_pointer = &some_fn;
    // to suppress "variable never muted" error
    _=&some_pointer;

    // this line throws a compiler error:
    // error: cannot load comptime-only type 'fn () void'
    // note: pointer of type *const fn () void is runtime-known
    const thread = try std.Thread.spawn(.{}, some_pointer.*, .{});
    thread.join();
}

It’s easier to make a closure in Zig that way. Just write a wrapper for this, pass it the function pointer as parameter and call it in the wrapper. And of course, if you are on Zig 0.16, prefer io.concurrent, but the same restriction applies there.

2 Likes

look at std.Thread #L417 The function return type must be comptime known to process termination

Thank you, I was also looking for a work-around lol.

And of course, if you are on Zig 0.16, prefer io.concurrent, but the same restriction applies there.

Why would I want to prefer that? (no hate, genuine curiosity) I am building a shell, and so far, I’m treating a command as such:

pub const command = struct {
    argv: []const []const u8,
    handle: union(enum) {
        builtin: std.Thread,
        external: std.process.Child,
    }
}

so that I can treat a builtin the same as an external command. I want to use threads for builtins so that the interface is similar.

where would I go to look at that?

in the std library, → Thread.zig line 417 ?

same place where your struct std.Thread comes from … ….

std.Thread

Thank you! Forgive my ignorance, I am relatively new to the zig ecosystem and programming forums, so I tend to not know where people find their info. I also am often unfamiliar with naming conventions.

I’m assuming you will need some synchronization, like mutexes, or waiting for the process to end. It’s hard in Zig 0.16 without using io.

True, but (for now at least), child.wait/thread.join foreach thread/process works well enough for me. I imagine in the future that will be the case, but I’m developing this for myself, so I want to try it this way first.

you can dodge this (modulo accepting similar calling type restrictions to pthread) by creating a wrapper function whose arguments are a function pointer and the arguments to the function and body which just calls the function.

2 Likes

That is the plan as of now!

1 Like

The type of a runtime function pointer is known including its return type.

It would be possible to modify the implementation of spawn and async/concurrent to accept function pointers.

With the planned feature to statically know the required stack size for a call, taking advantage of that would be easier with comptime known. If it were runtime known then the caller would either have to annotate the stack size which is error-prone, or use restricted function pointers which are not implemented yet.

That doesn’t explain why the thread api was originally like that, but does suggest it likely won’t change.

1 Like