Optionals

Optionals let us represent missing values. For example, ?u64 means “either a u64 or null”:

const std = @import("std");
const print = std.debug.print;

pub fn main() !void {
    var opt: ?u64 = null;
    print("opt is {?}\n", .{opt});
    opt = 4;
    print("opt is {?}\n", .{opt});
}
$ zig run optionals.zig
opt is null
opt is 4

We can can use .? to get an optional’s value, but that’s checked illegal behavior in case of null. We can use an if statement to handle both cases:

const std = @import("std");
const print = std.debug.print;

pub fn main() !void {
    const opt: ?u64 = 4;
    print("opt is {d}\n", .{opt.?});

    if (opt) |value| {
        print("got value: {}\n", .{value});
    } else {
        print("got null\n", .{});
    }
}
$ zig run if.zig
opt is 4
got value: 4

The orelse keyword replaces a null with a default value:

const std = @import("std");
const print = std.debug.print;

pub fn main() !void {
    var opt: ?u64 = null;
    print("opt or zero: {}\n", .{opt orelse 0});
    opt = 4;
    print("opt or zero: {}\n", .{opt orelse 0});
}
$ zig run orelse.zig
opt or zero: 0
opt or zero: 4

If we use an optional as the expression in a while loop, the loop ends when the value becomes null:

const std = @import("std");
const print = std.debug.print;

pub fn main() void {
    while (countdown()) |n|
        print("{}\n", .{n});
    print("Go!\n", .{});
}

var counter: u64 = 3;

fn countdown() ?u64 {
    if (counter == 0)
        return null;
    counter -= 1;
    return counter + 1;
}
$ zig run while.zig 
3
2
1
Go!

Next example: Structs.