DEV Community

Cover image for Zig vs Go: errors
Paolo Carraro
Paolo Carraro

Posted on

Zig vs Go: errors

(you can find previous post about same topic here)

Errors are values

As in Go, errors in Zig are also handled as values, but while in Go we can indicate that a function returns multiple values, so one of them could be a pointer to an error, in Zig instead we declare a sort of union called error union type: the ! symbol that precedes the type returned by a function indicates that we might get an error. We can also have a precise indication of the error type that is defined as an enum if in addition to the ! we also have the type specification.

// Go
func canFail(num int) (int, error) {
    if num > 10 {
        return num, errors.New("input is greater than max (10)") 
    }
    return num, nil
}
Enter fullscreen mode Exit fullscreen mode
// Zig
fn canFail(num: i8) !i8 {
    if (num > 10) {
        return error.InputIsGreaterThanMax;
    }
    return num;
}
Enter fullscreen mode Exit fullscreen mode

We can notice that the substantial differences lie in the fact that Zig returns the value of an enum (declared inline in this example) and that this is not coupled with the result but is mutually exclusive; this is better seen by observing how it is handled.

// Go
result, err := canFail(val)
if err != nil {
        fmt.Printf("An error occurs: %v\n", err)
        os.Exit(1)
    }
// handle result value
Enter fullscreen mode Exit fullscreen mode
// Zig
const result = canFail(num) catch |err| {
    std.debug.print("An error occurs: {}\n", .{err});
    return err;
};
// handle result value
Enter fullscreen mode Exit fullscreen mode

In Zig it is also possible to use a more concise formula when the error should not be handled but only returned to the previous step:

const result = try canFail(num);
// handle result value
Enter fullscreen mode Exit fullscreen mode

try in this case is the compressed version of catch |err| return err.

Error declaration

In Go, an error is any struct that implements the Error() string method, and to create custom errors we use these approaches.

// Go
var (
    ErrMissingArgument     = errors.New("missing argument")
    ErrInvalidArgument     = errors.New("invalid argument")
)

type MaxValueValidation struct {
    Max     int
    Current int
}

func (v *MaxValueValidation) Error() string {
    return fmt.Sprintf("input %d is greater than max %d", v.Current, v.Max)
}

Enter fullscreen mode Exit fullscreen mode

In Zig, however, the error is reduced to an enum that can be combined with other enums and the result used as an indication of possible return errors.

const InputError = error{
    WrongInput,
    MissingInput,
};

const ValidationError = error{
    InputGreaterThanMax,
};

const FailureError = InputError || ValidationError;
Enter fullscreen mode Exit fullscreen mode

Here are two complete examples of error handling:

// Go

package main

import (
    "errors"
    "fmt"
    "os"
    "strconv"
)

var (
    ErrMissingArgument = errors.New("missing argument")
    ErrInvalidArgument = errors.New("invalid argument")
)

type MaxValueValidation struct {
    Max     int
    Current int
}

func (v *MaxValueValidation) Error() string {
    return fmt.Sprintf("input %d is greater than max %d", v.Current, v.Max)
}

func main() {
    args := os.Args
    if len(args) < 2 {
        fmt.Printf("An error occurs: %v\n", ErrMissingArgument)
        os.Exit(1)
    }

    val, err := strconv.Atoi(os.Args[1])
    if err != nil {
        fmt.Printf("An error occurs: %v\n", ErrInvalidArgument)
        os.Exit(1)
    }

    result, err := canFail(val)
    var validationError *MaxValueValidation
    if errors.As(err, &validationError) {
        fmt.Printf("Check input: %v\n", validationError)
        os.Exit(1)
    } else if err != nil {
        fmt.Printf("An error occurs: %s\n", err.Error())
        os.Exit(1)
    } else {
        fmt.Printf("The result is %d\n", result)
    }
}

func canFail(num int) (int, error) {
    if num > 10 {
        return num, &MaxValueValidation{Max: 10, Current: num}
    }
    return num, nil
}

Enter fullscreen mode Exit fullscreen mode
// Zig
const std = @import("std");

const InputError = error{
    WrongInput,
    MissingInput,
};

const ValidationError = error{
    InputGreaterThanMax,
};

const FailureError = InputError || ValidationError;

pub fn main() !void {
    var args = std.process.args();
    _ = args.skip();

    const valueArg = args.next() orelse {
        std.debug.print("Error occurs: missing argument.\n", .{});
        return FailureError.MissingInput;
    };

    const num = std.fmt.parseInt(i8, valueArg, 10) catch |err| {
        std.debug.print("Error occurs: wrong input {}\n", .{err});
        return FailureError.WrongInput;
    };

    const result = try canFail(num);
    std.debug.print("The result is: {d}", .{result});
}

fn canFail(num: i8) FailureError!i8 {
    if (num > 10) {
        std.debug.print("input {d} is greater than max {d}\n", .{ num, 10 });
        return ValidationError.InputGreaterThanMax;
    }
    return num;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)