How to write a return type '!void' expanded

Hi everyone … new to zig

I’m exploring the language and I found something curious.

Example function

pub fn func1(a: u32) !void {
    _ = a;
}

It returns !void and this expands to @typeInfo(@typeInfo(@TypeOf(test1.func1)).Fn.return_type.?).ErrorUnion.error_set!void

I wanted to put the expanded form as my return type but it gives an error.

pub fn func1(a: u32) @typeInfo(@typeInfo(@TypeOf(test1.func1)).Fn.return_type.?).ErrorUnion.error_set!void {
    _ = a;
}

error: use of undeclared identifier 'test1'

That is the name of the file test1.zig but why is that in the type?
So, I removed that part.

pub fn func1(a: u32) @typeInfo(@typeInfo(@TypeOf(func1)).Fn.return_type.?).ErrorUnion.error_set!void {
    _ = a;
}

And I get a new error

error: dependency loop detected

So, how can I use the expanded form as a return type?

The expression

@typeInfo(@typeInfo(@TypeOf(func1)).Fn.return_type.?).ErrorUnion.error_set

isn’t an expanded form of the error set part of the return type of func1, it’s just a way to refer to the error set from somewhere else in the program. The compiler is giving you a dependency loop error because you can’t use the return type of func1 inside the definition of the return type of func1.

Interesting …

How I got to this point was:

const test1 = @import("test1.zig");
comptime {
    @compileLog(@TypeOf(@field(test1, "func1")));
}
Compile Log Output:
@as(type, fn(u32) @typeInfo(@typeInfo(@TypeOf(test1.func1)).Fn.return_type.?).ErrorUnion.error_set!void)

If that isn’t the true expansion, why is it returning this making it seem to be so and what should it be? What would work as an expanded form?

It’s because the error set is inferred. The explicit return type would be error{}!void because it can’t return any errors (or just void if you don’t want the return type to be an error union).

Error sets are of type type, so printing (or compileLoging) an error set is printing the name of the type. It seems that inferred error sets are distinct types, so you end up printing that verbose type name.

Even though both the inferred error set of foo1 and the explicit error set error{} are error set types with an empty list of errors, they are distinct types.

pub fn func1(a: u32) !void {
    _ = a;
}
test {
    @compileLog(error{} == @typeInfo(@typeInfo(@TypeOf(func1)).Fn.return_type.?).ErrorUnion.error_set);
}
Compile Log Output:
@as(bool, false)
1 Like

Thank you for your reply.

I am trying to compare function definitions in comptime. I’m trying to see if one struct has the same declarations in another.

The fact that they are distinct types will make this much harder to do.

Thanks for your help.

A bit annoying and verbose, but one way to print an inferred error set is to access its type info and print out the slice of errors. I definitely learned something looking into this!

pub fn func1(a: u32) !void {
    _ = a;
}
test {
    const ReturnType = @typeInfo(@TypeOf(func1)).Fn.return_type.?;
    const ReturnErrors = @typeInfo(ReturnType).ErrorUnion.error_set; 
    @compileLog(@typeInfo(ReturnErrors).ErrorSet);
}
Compile Log Output:
@as(?[]const builtin.Type.Error, { })

If you are interested in what is going on with @typeInfo, you can look at builtin.zig in the standard library, specifically the Type union. Of course that is kind of “in the weeds” and not super important to learning/using Zig day to day.

6 Likes