Sometimes a value may or may not exist.
A search may fail. A pointer may be empty. A function may have nothing useful to return.
Zig represents this with an optional type.
An optional type is written with ? before another type:
?i32This means:
either an
i32value, ornull
Here is a small example:
const std = @import("std");
pub fn main() void {
const a: ?i32 = 42;
const b: ?i32 = null;
std.debug.print("{any}\n", .{a});
std.debug.print("{any}\n", .{b});
}The output is:
42
nullAn optional type stores one of two states:
- a real value
null
The value must match the base type.
These are valid:
const x: ?i32 = 10;
const y: ?bool = true;
const z: ?f64 = null;This is not:
const n: ?i32 = "hello";because "hello" is not an integer.
Optional types are common in Zig because the language avoids hidden values and implicit failure states. A missing value is represented directly in the type.
For example, suppose we want a function that finds the first even number in an array.
If an even number exists, return it.
If none exists, return null.
const std = @import("std");
fn firstEven(numbers: []const i32) ?i32 {
for (numbers) |n| {
if (n % 2 == 0) {
return n;
}
}
return null;
}
pub fn main() void {
const values = [_]i32{ 1, 3, 7, 8, 9 };
const result = firstEven(&values);
std.debug.print("{any}\n", .{result});
}The output is:
8If the array contains no even numbers:
const values = [_]i32{ 1, 3, 5, 7 };the output becomes:
nullOptional types are different from error unions.
This:
?i32means:
a value may be missing
This:
error{Failed}!i32means:
an operation may fail
The difference matters.
A missing value is often normal behavior. An error usually means something went wrong.
Optional types are also used heavily with pointers:
?*i32This means:
either a pointer to
i32, ornull
This is similar to nullable pointers in C, but Zig makes the possibility explicit in the type system.
A plain pointer:
*i32cannot be null.
The compiler checks this rule.
Optional values must usually be unwrapped before use. That is the subject of the next section.
Exercise 9-1. Declare optional values of type ?bool, ?u32, and ?f64.
Exercise 9-2. Write a function that returns the first negative number in an array, or null if none exists.
Exercise 9-3. Change firstEven so it returns a pointer to the matching value instead of the value itself.
Exercise 9-4. Why is ?i32 safer than using -1 as a special “not found” value?