Generic function type return type

I had an idea to create a generic struct that looks like this:

pub fn Parser(
    Context: type,
    ParseError: type,
    parseValue: fn (ctx: Context, comptime T: type, arg: [:0]const u8) ParseError!T,
) type {
    // ...
}

The idea is that the Parser can be passed a custom function to parse args into a set of types, e.g integers, floats, etc. Unfortunately this does not compile due to ‘undeclared identifier T’. So I wrote a little test program:

const std = @import("std");

pub fn main() !void {
    const F = @TypeOf(f);
    std.debug.print("{any}\n", .{F});
    std.debug.print("{any}\n\n", .{@typeInfo(F).@"fn".return_type});

    std.debug.print("{d}\n", .{f(i32)});
    std.debug.print("{d}\n", .{f(f32)});
    std.debug.print("{s}\n", .{f([]const u8)});
    std.debug.print("{any}\n", .{f(bool)});
}

fn f(comptime T: type) T {
    return switch (T) {
        i32 => 1,
        f32 => 2.3,
        []const u8 => "foo bar",
        bool => true,
        else => comptime unreachable,
    };
}

Which outputs:

fn (comptime type) anytype
null
1
2.3
foo bar
true

Interesting. The return type is formatted as anytype (which you cannot define as a return type of a function), but actually, the return type is null, i.e there is no return type. There is even a ‘TODO’ in the source for std.builtin.Type.Fn which is relevant:

        /// TODO change the language spec to make this not optional.
        return_type: ?type,

This gives me hope that one day my original use case will be supported.

However, I tried anyway to assign f to a typed function. Trying to use T or anytype of course does not work, so I tried type:

    const g: fn (type) type = f;

    const G = @TypeOf(g);
    std.debug.print("{any}\n", .{G});
    std.debug.print("{any}\n\n", .{@typeInfo(G).@"fn".return_type});

    std.debug.print("{d}\n", .{g(i32)});
    std.debug.print("{d}\n", .{g(f32)});
    std.debug.print("{s}\n", .{g([]const u8)});
    std.debug.print("{any}\n", .{g(bool)});

Incredibly, this works!

fn (comptime type) anytype # @TypeOf(f)
null

fn (comptime type) type
type

1
2.3
foo bar
true

I then discovered that you can declare g’s return type as just about anything: (i32, void, std.ArrayList(u8)) and as long as it’s a valid, concrete type expression, Zig just accepts it, regardless of whether g can or will return that type!

This makes me wonder: is my original use case of declaring a function type’s type parameter as its return type planned? It “works” for now by just declaring the return type as ParseError!void or something, but this is obviously not ideal for a user-facing API. I could not find any github issues referring to this. I’m posting this to share my discovery and see if anyone has any insight on this, as it feels like a rough edge of the language.

1 Like

You can get return type of generic function like this @TypeOf(f(32)) the function won’t actually be evaluated, but to figure out the return type, the compiler must be aware of the generic parameters.

You can also use @TypeOf(@call(...)) as well… [1]

1: https://github.com/Cloudef/zig-aio/blob/master/src/coro/Scheduler.zig#L74-L78

1 Like