Zig 0.15.2
I’m writing a program that connects to a TCP address but I want to it to wait for the TCP Address to be available or not. Initially I had `try net.tcpConnectToAddress(address)` in my main function but obviously this exits the program immediately when the socket is not ready.
Currently the error return type of tcpConnectToAddress is an error set of `posix.SocketError || posix.ConnectError;`. I want to be able to return the error set posix.SocketError but match on the different types of ConnectError.
var stream: std.net.Stream = undefined;
while (true) {
stream = net.tcpConnectToAddress(address) catch |err| switch (err) {
.SocketError => return err,
.ConnectError => {
// Handle the specific .ConnectError errors
std.debug.print(“Waiting for socket on: {any}”, .{address.in});
// retry logic
},
};
Is there a way to match an error set in a switch statement and decompose another error set, to handle each error case individually? Or do I need to define all the errors cases (with a _ for the ones I don’t care about).
Note: I want to handle the errors in the SocketError set differently from the “rest” of the errors in the ConnectError set
there is no such thing as sub error sets, they are merged into a flat set and identified by their name, same name = same error.
the syntax is
switch (err)
ErrorSet.MyError => {},
//more common
error.MyError => {},
// _ has a separate meaning and is only valid for non exhaustive enums
else => {},
}
fyi, for the first part of your question, there’s a proposal for error set matching. It was accepted, then unaccepted, but still open (for now)
3 Likes
I see, so the only way to define different behavior for each of the error sets will be to manually define the matches for ConnectError and then use a catch all (_) for the SocketError. Unfortunately that can be error prone as then if a new error is added to the ConnectError set it will fall into the catch all.
Not very elegant, but it gets the job done.
pub fn inErrorSet(comptime err: anyerror, comptime Err: type) bool {
@setEvalBranchQuota(1500);
if (@typeInfo(Err).error_set) |error_set| inline for (error_set) |err_info| {
if (std.mem.eql(u8, @errorName(err), err_info.name)) {
return true;
}
};
return false;
}
const MayThrowError = std.posix.SocketError || std.posix.ConnectError;
fn mayThrow(flag: u8) MayThrowError!void {
return switch (flag) {
0 => error.AccessDenied,
1 => error.AddressUnavailable,
2 => error.SocketTypeNotSupported,
3 => {},
else => error.Unexpected,
};
}
fn handleMayThrow(flag: u8) std.posix.SocketError!bool {
mayThrow(flag) catch |err| {
const connect_err: std.posix.ConnectError = switch (err) {
inline else => |e| if (comptime inErrorSet(e, std.posix.ConnectError)) @errorCast(e) else return @errorCast(e),
};
return switch (connect_err) {
error.AccessDenied => true,
error.AddressUnavailable => true,
else => false,
};
};
return false;
}
test handleMayThrow {
try std.testing.expect(handleMayThrow(0) catch unreachable);
try std.testing.expect(handleMayThrow(1) catch unreachable);
try std.testing.expectError(error.SocketTypeNotSupported, handleMayThrow(2));
try std.testing.expect(!(handleMayThrow(3) catch unreachable));
try std.testing.expect(!(handleMayThrow(4) catch unreachable));
}
One of the reasons I prefer to use errors only to express basic error categories, while providing detailed error information through output parameters, is because of OP’s requirements. However, POSIX errors generally don’t change frequently, so there’s no need to worry too much about this specific issue.
I rescued some code from a rejected pull request, and put in about a day’s work on some kind of high-concept maximalist error sets library, before getting distracted (in other words, I lifted the function I actually needed out of the repo and went on with my life).
Here’s a snapshot of all that, it won’t specifically do “switch on error sets with coercion”, but there’s a function for “conditionally return an error coerced to a given error set, or null”, which can be used for the same purpose.
3 Likes