#switch on error set

1 messages · Page 1 of 1 (latest)

stark snow
#

How do I test if an error is part of a known error set?

const My_Error = error{ err_one, err_two, err_three };

fn foo(a: u32) !void {
    if (a == 0)
        return error.zero
    else
        return My_Error.err_one;
}

fn bar() !void {
    var a: u32 = 1;
    foo(a) catch |err| switch (err) {
        // How do I test if err is in My_Error?
        My_Error => return error.I_got_something_in_my_error,
        else => return err,
    };
}

pub fn main() !void {
    try bar();
}
#

Is there some way besides separately listing every element of the error set?

#

Maybe some inline magic?

agile shoal
#

I'm not aware of any built in way

#

You can probably do it with comptime though

stark snow
#

Oh right - like inline for over the fields of the error set?

agile shoal
#
const std = @import("std");

const E = error{Bar};

test {
    var err: anyerror = error.Bar;
    const in_e = inline for (@typeInfo(E).ErrorSet.?) |e| {
        if (err == @field(anyerror, e.name)) {
            break true;
        }
    } else false;
    std.debug.print("{}\n", .{in_e});
}
stark snow
#

Thank you!

bright lion
#

If I may, here's a version that should consume O(1) eval branch quota, instead of O(n):

const std = @import("std");
const assert = std.debug.assert;

inline fn setIsSubset(comptime SuperSet: type, comptime SubSet: type) bool {
    comptime {
        const super = @typeInfo(SuperSet).ErrorSet orelse return true; // SuperSet = anyerror
        const sub = @typeInfo(SubSet).ErrorSet orelse return false; // SuperSet != anyerror, SubSet = anyerror
        if (super.len < sub.len) return false; // subset can't have more members
        const Combo = SuperSet || SubSet;
        const combo = @typeInfo(Combo).ErrorSet.?;
        assert(super.len <= combo.len); // otherwise something is very wrong
        return super.len == combo.len; // true = all members in SubSet already present in SuperSet
    }
}

inline fn valueIsSubset(comptime SuperSet: type, value: anytype) bool {
    return setIsSubset(SuperSet, @TypeOf(value));
}

test {
    try std.testing.expect(valueIsSubset(error{Foo, Bar, Baz}, error.Foo));
    try std.testing.expect(!valueIsSubset(error{Foo, Bar, Baz}, error.Buzz));
    try std.testing.expect(!valueIsSubset(error{Foo, Bar, Baz}, @as(error{ Foo, Fizz }, error.Foo)));
}
stark snow
#

Oh ho ho that is very clever

agile shoal
#

heh, neat idea but I don't think it quite answers the question

#

As your last test case demonstrates, it checks types, not values

stark snow
#

Oh right so it works at comptime but not if you don't know what error is being returned?

agile shoal
#

Whereas the question was about checking whether the error is part of a given set, at runtime

agile shoal
#

You could combine it with inline else to make it work, but that might generate a lot of unnecessary code

bright lion
#

fair, though that can be worked around doing switch (err) { inline else => |e| isSubset(SuperSet, e) }, which brings it up to O(n) on par with the original suggestion

bright lion
agile shoal
#

No, it'll generate a branch for every possible error

#

inline for only generates branches for each error in the target set

bright lion
#

right

stark snow
#

Good point but I'm running into trouble where I think I'm being too clever for the compiler to figure out:

#

(sorry having trouble reproducing the error in the example)

#
const My_Error = error{ err_one, err_two, err_three };

pub inline fn isMy_Error(err: anyerror) bool {
    inline for (@typeInfo(My_Error).ErrorSet.?) |e| {
        if (err == @field(My_Error, e.name))
            return true;
    }

    return false;
}

fn foo(a: u32) !void {
    if (a == 0)
        return error.zero
    else
        return My_Error.err_one;
}

const Bar_Error = error{ zero, I_got_something_in_my_error };

fn bar() Bar_Error!void {
    var a: u32 = 1;
    foo(a) catch |err| {
        if (isMy_Error(err)) {
            return error.I_got_something_in_my_error;
        }
        return err;
    };
}

pub fn main() !void {
    try bar();
}
#

(how do I get syntax highlighting?)

#

In this case the function is working but the type inference fails to understand that bar() can only return Bar_Error things

#

got it to work with inline else:

#
    foo(a) catch |err| switch (err) {
        inline else => |e| {
            if (isMy_Error(e))
                return error.I_got_something_in_my_error;
        },

#

whoops nevermind

#
        inline else => |e| {
            if (isMy_Error(e)) {
                return error.I_got_something_in_my_error;
            } else {
                return err;
            }
        },

#

that still gives the type inference error