Potato Blog


Zig Optionals

Optional types in zig are the only type that is allowed to be null. Optionals wrap a child type. To access the child value you must unwrap the optional. This prevents accidental null pointer dereferencing. Let’s declare and unwrap them.

Declaring Optionals

Use ?T syntax to declare an optional of type T. This can store either null or a value of type T.

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

pub fn main() void {
    const maybe_value: ?i32 = null;
    print("{any}\n", .{maybe_value}); // null
}

Unwrapping Optionals

Method 1: if expression

Unwrap an optional using an if expression with or without a capture.

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

fn giftsRemaining() ?usize {
    const quantity = rand.intRangeAtMost(usize, 0, 1);
    if (quantity == 0) return null;
    return quantity;
}

pub fn main() void {
    const gifts_remaining = giftsRemaining();

    // The capture unwraps the optional and extracts the child value.
    if (gifts_remaining) |quantity| {
        print("There is {d} gift remaining.\n", .{quantity}); // There is 1 gift remaining.
    } else {
        print("No gifts.\n", .{});
    }
}

Method 2: while expression

Unwrap an optional using a while expression with or without a capture.

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

var health_remaining: usize = 100;

fn attack(dmg: usize) ?usize {
  if (health_remaining == 0) return null;
  health_remaining -= dmg;
  return health_remaining;
}

pub fn main() void {
  while (attack(25)) |health| {
    print("Health Remaining: {d}\n", .{health}); // 75, 50, 25, 0
  }
}

Method 3: orelse expression

The orelse expression will set a fallback value when the optional is null.

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

pub fn main() void {
    const dog: ?[]const u8 = null;
    const sound = dog orelse "bark";

    print("Dog says: {s}\n", .{sound}); // Dog says: bark
}

Method 4: .? shorthand

The .? operator asserts the optional is not null and extracts the child value. .? is shorthand for orelse unreachable.

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

pub fn main() void {
    const score: ?usize = 50;
    const new_score = score.? + 1;

    print("Score: {d}\n", .{new_score}); // Score: 51
}