I’m trying to find an easy way to get the type of func
that would go into @call(.auto, func, args)
. For fixed size args, I could probably use fn (@TypeOf(args[0]), @TypeOf(args[1])) void
, but how do I do it for generic Args
tuple? The only way I can think if is to build type type using std.builtin.Type.Fn
but that gets super verbose. Is there an easier way to do it?
The source code to std.fmt.format is a good guide to how to do type reflection on an anytype
args tuple.
It is not clear to me what you are trying to do.
@TypeOf(func)
You can use std.meta.ArgsTuple
to get the function arguments tuple type.
const F = @TypeOf(func);
const T = std.meta.ArgsTuple(F);
// var args: T = ...;
const ti = @typeInfo(F).Fn; // 0.13.0
// const ti = @typeInfo(F).@"fn"; // 0.14.0-dev
inline for (ti.params) |param| {
const PT = param.type.?;
// ...
}
@call(.auto, func, args);
I want to define a generic struct based on Args
tuple, that contains an instance of the args tuple and a function pointer that receives the args tuple.
Here is the snippet what I’m trying to achieve:
pub fn Callback(comptime Caller: type, comptime Args: type) type {
return struct {
args: Args,
func: *const fn (*Caller, <somehow expand Args here>) void,
pub fn call(self: @This(), caller: *Caller) void {
@call(.auto, self.func, .{caller} ++ self.args);
}
};
}
The thing is, i don’t know how to define the function pointer type. Any example of such closures that I look for in the std
, such std.Thread.Pool
, just uses anonymous struct and the function is just anytype
.
I know I can somehow construct the type using std.builtin.Type
, but I wondered if there is an easier way.
I think it depends on how you want to use this type and what you can provide as parameters to Callback
, here I created 2 solutions the first constructs the type with @Type
, the second instead expects you to provide the function pointer type to Callback
and then extracts all the needed type information from that function pointer type:
const std = @import("std");
const Example = struct {
a: u32,
pub fn something(self: *Example, b: u32, c: u32) void {
self.a += b + c;
}
};
pub fn Fn(comptime Caller: type, comptime Args: anytype) type {
var params: [1 + Args.len]std.builtin.Type.Fn.Param = undefined;
params[0] = .{
.is_generic = false,
.is_noalias = false,
.type = *Caller,
};
for (Args, 1..) |T, i| {
params[i] = .{
.is_generic = false,
.is_noalias = false,
.type = T,
};
}
return @Type(.{ .Fn = .{
.calling_convention = .Unspecified,
.is_generic = false,
.is_var_args = false,
.return_type = void,
.params = ¶ms,
} });
}
pub fn FnPtr(comptime Caller: type, comptime Args: anytype) type {
return @Type(.{ .Pointer = .{
.size = .One,
.is_const = true,
.is_volatile = false,
.alignment = 1,
.address_space = .generic,
.child = Fn(Caller, Args),
.is_allowzero = false,
.sentinel = null,
} });
}
pub fn ArgsTuple(comptime Args: anytype) type {
var types: [Args.len]type = undefined;
for (Args, 0..) |a, i| types[i] = a;
return std.meta.Tuple(&types);
}
pub fn Callback(comptime Caller: type, comptime Args: anytype) type {
return struct {
args: ArgsTuple(Args),
// func: *const fn (*Caller, <somehow expand Args here>) void,
func: FnPtr(Caller, Args),
pub fn call(self: @This(), caller: *Caller) void {
@call(.auto, self.func, .{caller} ++ self.args);
}
};
}
pub fn main() !void {
{
var example: Example = .{ .a = 4 };
const SomethingCallback = Callback(Example, .{ u32, u32 });
const something_cb: SomethingCallback = .{
.args = .{ 7, 11 },
.func = &Example.something,
};
std.debug.print("example before: {}\n", .{example});
something_cb.call(&example);
std.debug.print("example after: {}\n", .{example});
}
{
var example: Example = .{ .a = 4 };
const SomethingCallback = EasyCallback(@TypeOf(&Example.something));
const something_cb: SomethingCallback = .{
.args = .{ 7, 11 },
.func = &Example.something,
};
std.debug.print("example before: {}\n", .{example});
something_cb.call(&example);
std.debug.print("example after: {}\n", .{example});
}
}
pub fn EasyCallback(comptime FuncPtr: type) type {
return struct {
args: ArgsFromFuncPtr(FuncPtr),
func: FuncPtr,
pub fn call(self: @This(), caller: CallerFromFuncPtr(FuncPtr)) void {
@call(.auto, self.func, .{caller} ++ self.args);
}
};
}
pub fn ArgsFromFuncPtr(comptime FuncPtr: type) type {
const child = std.meta.Child(FuncPtr);
const info = @typeInfo(child);
const params = info.Fn.params;
const len = params.len;
if (len < 1) @compileError("functions need to have at least one parameter");
var types: [len - 1]type = undefined;
for (1..len) |i| types[i - 1] = params[i].type.?;
return std.meta.Tuple(&types);
}
pub fn CallerFromFuncPtr(comptime FuncPtr: type) type {
const child = std.meta.Child(FuncPtr);
const info = @typeInfo(child);
const params = info.Fn.params;
const len = params.len;
if (len < 1) @compileError("functions need to have at least one parameter");
return params[0].type.?;
}
This is really awesome. Thank you very much!
You can pass the function as anytype
parameter in Callback
.
pub fn Callback(comptime Caller: type, comptime func: anytype, comptime Args: std.meta.ArgsTuple(func)) type {
return struct {
args: Args,
pub fn call(self: @This(), caller: *Caller) void {
@call(.auto, func, .{caller} ++ self.args);
}
};
}
I can’t do this, because I don’t know the function at the time I’m setting up the struct that is holding the callback. I just know the signature (ArgsTuple), but not the actual function. The function can be runtime dynamic, but the signature is comptime static.
@Sze’s solution is perfect for my use case.