Learning Zig, can't figure out error handling

Trying to learn Zig, but not understanding quite how to handle errors. I have this init function that I want to validate the input parameters to, if the values are invalid I want to raise an error.

In test “Valid limits” I call the function with try Limits(i32).init(100, 20);, however, I can’t do that in test “Invalid limits” because I get the error error: expected error union type, found regulators.pid.Limits(i32). Which is why I have const limit = Limits(i32).init(20, 100);. That works.

So I’m assuming I test the limit variable wrong? Since I probably should call const limit = try Limits(i32).init(20, 100); in “Invalid limits” test also.

(Side note: Python user describes their code as “Pythonic” when its well written, does Zig have a similar lingo?)

Current code.

const std = @import("std");

const LimitError = error{InvalidLimits};

pub fn Limits(comptime T: type) type {
    return struct {
        high: T,
        low: T,

        pub fn init(high: T, low: T) LimitError!Limits(T) {
            if (high <= low) return LimitError.InvalidLimits;
            return Limits(T){ .high = high, .low = low };
       }
    };
}

test "Valid limits" {
    const limit = try Limits(i32).init(100, 20);
    try std.testing.expect(limit.high == 100);
}

test "Invalid limits" {
    const limit = Limits(i32).init(20, 100);
    try std.testing.expectError(LimitError.InvalidLimits, limit);
}
2 Likes

Your Invalid limits test seems correct to me.

try Limits(i32).init(...) is equivalent to Limits(i32).init(...) catch |err| return err;, so @TypeOf(limit) after try Limits(i32).init would be Limits(i32) (the error would have already been handled by the try, i.e. the test would fail if the function returned an error), while the @TypeOf(limit) after Limits(i32).init (without the try) would be LimitError!Limits(i32) (the error information would still be available and would need to be handled in some way before being able to use the Limits(i32))

The only difference with your code compared to most expectError usage within the Zig standard library is that it usually omits the intermediate variable (but that’s purely a style/personal preference thing):

test "Invalid limits" {
    try std.testing.expectError(LimitError.InvalidLimits, Limits(i32).init(20, 100));
}

Some links that might help:

https://ziglang.org/documentation/master/#Error-Union-Type
https://ziglang.org/documentation/master/#try

4 Likes

With regards to language lingo, I’ve only seen idiomatic Zig being used here and there, so I’m just going to throw this proposal out here: Ziguanic. :lizard:

4 Likes

Haha - sounds similar to what people did with the term “Pythonic” - I may start using Ziguanic unironically to see if it catches on :slight_smile:

@squeek502 is right - it’s getting unpacked by the try statement.

3 Likes

Zig does not allow you to implicitly ignore or forget about handling errors. Consider for example the error message when attempting to discard an error:

error: error is discarded
    _ = canError();
        ~~~~~~~~^~
note: consider using 'try', 'catch', or 'if'

Similarly, storing error unions in memory via a const/var might allow you to forget (ironically) to deal with the error and as such Zig doesn’t allow it. You are however allowed to explicitly ignore errors if you wish, with catch {} or catch unreachable

3 Likes

Yes, this is true but with one caveat - catch unreachable can put you in undefined behavior if you go to ReleaseFast - see: Why `?` and `orelse unreachable` behave inconsistently under ReleaseFast/ReleaseSmall - #7 by kristoff

If you want to discard error conditions if they can occur, you should opt for “catch { }” because it explicitly states “do nothing if we get an error”. That’s different than saying “this block of code should never be reached”.

4 Likes

Thanks for the help!

+1 for Ziguanic :lizard: !

3 Likes

Of course :slight_smile: I’m going to mark the answer made by @squeek502 as the solution to this question. Feel free to open adjacent threads if you have more questions.

2 Likes

Ziguanic

:kissing_smiling_eyes: :ok_hand: :sparkles:

3 Likes

Zigantic :upside_down_face:

A solution that answers one of my questions,
having functions used in a routine, well loop, there I can use it and put catch .{} , but it will still throw the error just that in my internal routine I don’t need to handle it.
i think i am right??? .
if true, I will resume my project

hmm, it’s more complicated than that catch{} doesn’t work everywhere