Skip to content

Error set

Chung Leong edited this page Jun 2, 2024 · 6 revisions

Error sets are used by Zig functions to indicate they've failed to perform an operation for one reason or another. They are like enums, unique integers with assigned meanings. In JavaScript, they are represented as unique Error objects. Typically they form part of an error union returned by a function:

pub const FileOpenError = error{
    access_denied,
    out_of_memory,
    file_not_found,
};

pub const AllocationError = error{
    out_of_memory,
};

pub fn fail(reason: u32) !bool {
    return switch (reason) {
        1 => FileOpenError.access_denied,
        2 => FileOpenError.out_of_memory,
        3 => FileOpenError.file_not_found,
        else => false,
    };
}
import { FileOpenError, fail } from './error-set-example-1.zig';

try {
  fail(2);
} catch (err) {
  console.log(err.message);
  console.log(err === FileOpenError.out_of_memory);
}
Out of memory
true

Errors with the same name are represented by the same error object. They can appear in multiple error sets. Error objects are not instances of the error set class containing them (unlike enum items):

import { AllocationError, FileOpenError } from './error-set-example-1.zig';

console.log(FileOpenError.out_of_memory === AllocationError.out_of_memory);
console.log(FileOpenError.out_of_memory instanceof FileOpenError);
console.log(AllocationError.out_of_memory instanceof AllocationError);
true
false
false

Use the in operator to check whether an error is in an error set instead:

import { AllocationError, fail } from './error-set-example-1.zig';

for (let i = 1; i <= 3; i++) {
  try {
    fail(i);
  } catch (err) {
    console.log(`${err.message}: ${err in AllocationError}`);
  }
}
Access denied: false
Out of memory: true
File not found: false

As you can see in the example above, Zigar automatically derive error messages by inserting spaces between words of names in snake_case and camelCase.

Error casting

You can obtain the numeric value of an error through casting:

import { AllocationError, FileOpenError } from './error-set-example-1.zig';

console.log(Number(FileOpenError.access_denied));
console.log(Number(FileOpenError.out_of_memory));
console.log(Number(FileOpenError.file_not_found));
console.log(Number(AllocationError.out_of_memory));
28
29
30
29

Note how FileOpenError.out_of_memory and AllocationError.out_of_memory give us the same number. This is because they are the same object.

Errors can be casted into strings as well:

import { AllocationError, FileOpenError } from './error-set-example-1.zig';

console.log(String(FileOpenError.access_denied));
console.log(String(FileOpenError.out_of_memory));
console.log(String(FileOpenError.file_not_found));
console.log(String(AllocationError.out_of_memory));
Error: Access denied
Error: Out of memory
Error: File not found
Error: Out of memory

The reverse is also possible. Strings and numbers can be casted into errors:

import { FileOpenError } from './error-set-example-1.zig';

console.log(FileOpenError(47));
console.log(FileOpenError('Error: Access denied'));
console.log(FileOpenError('access_denied'));
console.log(FileOpenError(42));
[ZigError: Access denied] { number: 28 }
[ZigError: Access denied] { number: 28 }
[ZigError: Access denied] { number: 28 }
undefined

undefined is returned when there is no matching result.

JSON output

Calling JSON.stringify() on a Zig error object would yield a string formatted in a manner commonly used by web applications:

import { fail } from './error-set-example-1.zig';

try {
  fail(3);
} catch (err) {
  console.log(JSON.stringify(err));
}
{"error":"File not found"}

This string, once parsed, can be casted back into an error object:

import { FileOpenError } from './error-set-example-1.zig';

console.log(FileOpenError(JSON.parse('{"error":"File not found"}')));
[ZigError: File not found] { number: 30 }

anyerror

anyerror is a special error set in Zig that contains all errors in a given library. Exporting it would give you a mean to access all public errors:

pub const FileOpenError = error{
    access_denied,
    out_of_memory,
    file_not_found,
};

pub const HumanError = error{
    got_into_crypto_currencies,
    ran_out_of_beer,
    did_not_know_how_to_use_a_condom,
    hung_out_with_clifford_banes,
};

pub const AnyError = anyerror;
import { AnyError } from './error-set-example-2.zig';

for (const [ name, err ] of AnyError) {
  console.log(err.message);
}
Access denied
Out of memory
File not found
Got into crypto currencies
Ran out of beer
Did not know how to use a condom
Hung out with clifford banes

Note that making anyerror public does not make all errors public. If you have a function that explicitly returns an error union with anyerror, it's possible that Zigar would receive an error number that it knows nothing about:

const PrivateError = error{just_being_evil};

pub fn fail() anyerror!bool {
    return PrivateError.just_being_evil;
}
import { fail } from './error-set-example-3.zig';

try {
  fail();
} catch (err) {
  console.log(err.message);
}
Error number does not corresponds to any error in error set anyerror: 37

If you simply leave out the error set type and let the compiler infer the type, that type would be made public implicitly and thus visible to Zigar:

const PrivateError = error{just_being_evil};

pub fn fail() !bool {
    return PrivateError.just_being_evil;
}
import { fail } from './error-set-example-4.zig';

try {
  fail();
} catch (err) {
  console.log(err.message);
}
Just being evil

Error union

Clone this wiki locally