Not able to create ErrorSet after removal @Type?

I used to use the following function to generate error sets from enums, this is very useful when creating wrappers for c libraries.

fn EnumError(comptime E: type) type {
    switch (@typeInfo(E)) {
        .Enum => {
            const tag_count = std.meta.fields(E).len;
            var error_tags: [tag_count]std.builtin.Type.Error = undefined;

            for (std.meta.fields(E), 0..) |enum_field, index| {
                error_tags[index] = .{ .name = enum_field.name };
            }

            const err_set: std.builtin.Type = .{ .ErrorSet = error_tags[0..] };
            return @Type(err_set);
        },
        else => @compileError("EnumError only accepts enum types."),
    }
}

That than could be used like this:

const SomeResultCodesFromCLib = enum {
    some_error,
    some_other_error,
};

pub const CLibError = EnumError(SomeResultCodesFromCLib);

pub fn functionWithError() CLibError!void {}

But with the removal of @Type I don’t see a way to generate types of error set’s any more. I also did not see any related issues etc.

Is there any alternative for this? Or is the new way to not allow it and to just use a gigantic switch statement?

If not planned I’m curious of the reasoning behind it.

Maybe there are better solutions, but try this:

fn EnumError(comptime E: type) type {
    switch (@typeInfo(E)) {
        .@"enum" => {
            var T: type = error{};
            for (std.meta.fields(E)) |enum_field| {
                T = T || @TypeOf(@field(anyerror, enum_field.name));
            }
            return T;
        },
        else => @compileError("EnumError only accepts enum types."),
    }
}
4 Likes

The general idea was to replace @Type with specific functions, but only for those types which cannot be created in a more usual way.

The method @rpkak demonstrates is perfectly adequate, so that’s probably why there’s no @ErrorSet type. It’s actually less ceremony than the original method you were using for @Type, even if the O(n²)-ness of the algorithm makes me wince a bit. It could be optimized, no idea if it is, but in any case it won’t affect runtime.

1 Like

the switch should just be an access to @typeInfo(E).@"enum", imo, since the else branch duplicates or does less well than the default compile error for bad union access

Thanks! Even more concise than my previous method.

Makes sense, though I would argue that for consistency it might have been better to provide it for all types.

1 Like

That was @Type() :slight_smile:

The argument is that the ability to do @Type(.{ .optional = Foo }) means there’s an extra-canonical, funny-looking way to make every type, but there’s never any reason not to simply write ?Foo.

So the decision was made to break @Type apart into a collection of category-specific builtins, only for the type categories where it’s useful to have.

Fair enough, though I thought that the property of “the inverse of @typeInfo is @Type” was pretty elegant. Arguably worth preserving at the expense of more than one way to do things, but that isn’t what’s happening.

I suggested just making redundant use of @Type into a compile error which tells the user how to do it, like

error: @Type will not create an optional, use ?T

But promoting the tag to a builtin does reduce verbosity, just @Struct(... instead of @Type(.{ .@"struct" = ..., and breaking it up for parts is easier to document, so this is probably the better outcome.

I am confused. Why not just create the error set directly?

so instead of

const SomeResultCodesFromCLib = enum {
    some_error,
    some_other_error,
};

pub const CLibError = EnumError(SomeResultCodesFromCLib);

pub fn functionWithError() CLibError!void {}

why not

const CLibError = error {
    some_error,
    some_other_error,
};

pub fn functionWithError() CLibError!void {}

what am I missing?

presumably, the goal is to create an error set with a comptime-known (but not explicitly typed out) set of error names which are available in the program at compile time as strings but might be generated by comptime code execution. you might imagine this could save significant typing if you wanted to create, for example, 256 different errors, each with a 20-character name.

now, is this a good idea? no, probably it’s usually a bad one, even.

If your error sets are so big that quadratic time is a problem, you have a bigger and more important problem somewhere else.

1 Like