Optionals

    The question mark symbolizes the optional type. You can convert a type to an optional type by putting a question mark in front of it, like this:

    Now the variable could be an i32, or null.

    Instead of integers, let's talk about pointers. Null references are the source of many runtime exceptions, and even stand accused of being .

    Zig does not have them.

    Instead, you can use an optional pointer. This secretly compiles down to a normal pointer, since we know we can use 0 as the null value for the optional type. But the compiler can check your work and make sure you don't assign null to something that can't be null.

    Task: call malloc, if the result is null, return null.

    C code

    1. // malloc prototype included for reference
    2. void *malloc(size_t size);
    3. struct Foo *do_a_thing(void) {
    4. char *ptr = malloc(1234);
    5. if (!ptr) return NULL;
    6. // ...
    7. }

    Zig code

    1. // malloc prototype included for reference
    2. extern fn malloc(size: size_t) ?*u8;
    3. fn doAThing() ?*Foo {
    4. const ptr = malloc(1234) orelse return null;
    5. // ...
    6. }

    Here, Zig is at least as convenient, if not more, than C. And, the type of "ptr" is *u8 not ?*u8. The orelse keyword unwrapped the optional type and therefore is guaranteed to be non-null everywhere it is used in the function.

    The other form of checking against NULL you might see looks like this:

    1. // do some stuff
    2. if (optional_foo) |foo| {
    3. doSomethingWithFoo(foo);
    4. }
    5. // do some stuff
    6. }

    Once again, the notable thing here is that inside the if block, foo is no longer an optional pointer, it is a pointer, which cannot be null.

    One benefit to this is that functions which take pointers as arguments can be annotated with the "nonnull" attribute - __attribute__((nonnull)) in GCC. The optimizer can sometimes make better decisions knowing that pointer arguments cannot be null.

    An optional is created by putting ? in front of a type. You can use compile-time reflection to access the child type of an optional:

    test.zig

    1. const assert = @import("std").debug.assert;
    2. test "optional type" {
    3. // Declare an optional and implicitly cast from null:
    4. var foo: ?i32 = null;
    5. // Implicitly cast from child type of an optional
    6. foo = 1234;
    7. // Use compile-time reflection to access the child type of the optional:
    8. }

    Just like , null has its own type, and the only way to use it is to cast it to a different type:

    1. const optional_value: ?i32 = null;

    test.zig

    1. const assert = @import("std").debug.assert;
    2. test "optional pointers" {
    3. // Pointers cannot be null. If you want a null pointer, use the optional
    4. // prefix `?` to make the pointer type optional.
    5. var ptr: ?*i32 = null;
    6. var x: i32 = 1;
    7. ptr = &x;
    8. assert(ptr.?.* == 1);
    9. // Optional pointers are the same size as normal pointers, because pointer
    10. // value 0 is used as the null value.
    11. }