Skip to content

The Optional Type

Sometimes a value may or may not exist.

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:

?i32

This means:

either an i32 value, or null

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
null

An optional type stores one of two states:

  1. a real value
  2. 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:

8

If the array contains no even numbers:

const values = [_]i32{ 1, 3, 5, 7 };

the output becomes:

null

Optional types are different from error unions.

This:

?i32

means:

a value may be missing

This:

error{Failed}!i32

means:

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:

?*i32

This means:

either a pointer to i32, or null

This is similar to nullable pointers in C, but Zig makes the possibility explicit in the type system.

A plain pointer:

*i32

cannot 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?