Optional types in zig are the only type that is allowed to be null. You can think of an Optional as a type that says “this value may or may not exist” and wraps some child type/value. The act of unwrapping tells you whether there’s a value there. This behavior prevents accidental null pointer dereferencing.
There are four ways to unwrap Optionals and access the underlying value. We’re going to declare an Optional then get into ways to unwrap it and access the child type.
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
}