Is there an easy way to get fn type info with multiple args?

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 = &params,
    } });
}

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.?;
}
3 Likes

This is really awesome. Thank you very much!

1 Like

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.

1 Like