Skip to content

Unwrapping Optionals

An optional value cannot be used as the payload type until it is unwrapped.

An optional value cannot be used as the payload type until it is unwrapped.

This is wrong:

const x: ?i32 = 10;
const y: i32 = x; // error

x is not an i32. It is a ?i32.

To get the integer out, test the optional:

const std = @import("std");

pub fn main() void {
    const x: ?i32 = 10;

    if (x) |value| {
        std.debug.print("{d}\n", .{value});
    }
}

The syntax

if (x) |value| {
    ...
}

means: if x contains a value, bind that value to the name value.

Inside the block, value has type i32, not ?i32.

If the optional is null, the block is skipped.

const std = @import("std");

pub fn main() void {
    const x: ?i32 = null;

    if (x) |value| {
        std.debug.print("{d}\n", .{value});
    }
}

This program prints nothing.

Use else to handle the missing case:

const std = @import("std");

pub fn main() void {
    const x: ?i32 = null;

    if (x) |value| {
        std.debug.print("value: {d}\n", .{value});
    } else {
        std.debug.print("no value\n", .{});
    }
}

The output is:

no value

An optional can also be unwrapped with orelse.

const std = @import("std");

pub fn main() void {
    const x: ?i32 = null;
    const y: i32 = x orelse 0;

    std.debug.print("{d}\n", .{y});
}

The expression

x orelse 0

means: use the value inside x if it exists; otherwise use 0.

Here y is an i32.

orelse is useful for defaults:

fn portOrDefault(port: ?u16) u16 {
    return port orelse 8080;
}

If port has a value, that value is returned. If port is null, the function returns 8080.

There is also a force unwrap operator:

const x: ?i32 = 10;
const y: i32 = x.?;

The expression x.? extracts the payload.

It is safe only if x is not null.

This will fail at runtime in safe builds:

const x: ?i32 = null;
const y: i32 = x.?;

Use .? only when the program has already proved that the optional is not null.

For example:

const std = @import("std");

pub fn main() void {
    const x: ?i32 = 10;

    if (x != null) {
        const y = x.?;
        std.debug.print("{d}\n", .{y});
    }
}

This works, but the payload capture form is clearer:

if (x) |value| {
    std.debug.print("{d}\n", .{value});
}

Prefer the capture form when possible. It keeps the proof and the value together.

Exercise 9-8. Write a function valueOrZero that takes ?i32 and returns i32.

Exercise 9-9. Write a function printOptionalName that takes ?[]const u8 and prints either the name or "(none)".

Exercise 9-10. Rewrite a force unwrap with an if capture.