Skip to content

Error union

Chung Leong edited this page Apr 24, 2024 · 13 revisions

An error union holds either a value or an error. In normal usage you'll not encounter standalone error-union objects. Upon access they would be resolved automatically, either producing their assigned values or causing errors to be thrown:

const MathError = error{negative_number};
pub fn getSquareRoot(number: f64) MathError!f64 {
    if (number < 0) {
        return MathError.negative_number;
    }
    return @sqrt(number);
}
import { sqrt } from './error-union-example-1.zig';

try {
    console.log(`sqrt(36) = ${getSquareRoot(36)}`};
    console.log(`sqrt(-36) = ${getSquareRoot(-36)}`};
} catch (err) {
    console.log(err.message);
}
sqrt(36) = 6
Negative number

Error-union arrays

It's possible to have an array of error unions, each representing the outcome of an individual operation:

const std = @import("std");

const MathError = error{negative_number};

pub fn getSquareRoots(allocator: std.mem.Allocator, numbers: []const f64) ![]MathError!f64 {
    const results = try allocator.alloc(MathError!f64, numbers.len);
    for (numbers, results) |number, *result_ptr| {
        result_ptr.* = if (number >= 0) @sqrt(number) else MathError.negative_number;
    }
    return results;
}
import { getSquareRoots } from './error-union-example-2.zig';

const numbers = [ 1, 2, 3, -4, 5 ];
try {
    const sqrts = getSquareRoots(numbers);
    for (const [ index, sqrt ] of sqrts.entries()) {
        const number = numbers[index];
        console.log(`sqrt(${number}) = ${sqrt}`);
    }
} catch (err) {
    console.log(err.message);
}
sqrt(1) = 1
sqrt(2) = 1.4142135623730951
sqrt(3) = 1.7320508075688772
Negative number

The return type of getSquareRoots() might look a little odd with its two exclamation marks. It is an error union of an array of error unions. The outer error union captures potential out-of-memory error during allocation of the result array. The array itself captures errors of the individual square-rooting operations. When we loop through the results, we are able to process the first three numbers. The fourth number causes an exception, terminating the loop.

You can ask the iterator to return an error instead of throwing it by passing { error: 'return' } to entries().

import { getSquareRoots } from './error-union-example-2.zig';

const numbers = [ 1, 2, 3, -4, 5 ];
const sqrts = getSquareRoots(numbers);
for (const [ index, sqrt ] of sqrts.entries({ error: 'return' })) {
    const number = numbers[index];
    console.log(`sqrt(${number}) = ${sqrt}`);
}
sqrt(1) = 1
sqrt(2) = 1.4142135623730951
sqrt(3) = 1.7320508075688772
sqrt(-4) = Error: Negative number
sqrt(5) = 2.23606797749979

JSON representation

JSON.stringify() converts errors stored within error unions to objects of the form { error: [ERROR MESSAGE] }:

pub const AuthenticationError = error{
    UnknownUserNameOrPassword,
    UnverifiedAccount,
    TwoFactorFailure,
};

pub const AuthorizationError = error{
    InsufficientPriviledges,
    TemporarilySuspended,
    PermanentlyBanned,
};

pub const LoginResult = struct {
    authentication: AuthenticationError!bool,
    authorization: AuthorizationError!bool,
};

pub fn login() LoginResult {
    return .{
        .authentication = true,
        .authorization = AuthorizationError.PermanentlyBanned,
    };
}
import { login } from './error-union-example-3.zig';

const result = login();
const json = JSON.stringify(result, undefined, 2);
console.log(json);
{
  "authentication": true,
  "authorization": {
    "error": "Permanently banned"
  }
}

Error set