Skip to content

Exercises

Exercise 9-17. Declare an optional integer.

Exercise 9-17. Declare an optional integer.

const x: ?i32 = 10;

Change it to null.

const x: ?i32 = null;

Print both forms with {any}.

Exercise 9-18. Write a function firstPositive that returns the first positive number in a slice.

fn firstPositive(values: []const i32) ?i32 {
    for (values) |v| {
        if (v > 0) {
            return v;
        }
    }

    return null;
}

Test it with:

const a = [_]i32{ -3, -2, 0, 5, 9 };
const b = [_]i32{ -3, -2, 0 };

Exercise 9-19. Write a function indexOf that returns the index of a byte in a string.

fn indexOf(s: []const u8, target: u8) ?usize {
    for (s, 0..) |c, i| {
        if (c == target) {
            return i;
        }
    }

    return null;
}

Use it like this:

const pos = indexOf("hello", 'l');

if (pos) |i| {
    std.debug.print("found at {d}\n", .{i});
} else {
    std.debug.print("not found\n", .{});
}

Exercise 9-20. Rewrite this code without force unwrapping.

const x: ?i32 = 7;
const y = x.?;
std.debug.print("{d}\n", .{y});

Use optional capture:

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

Exercise 9-21. Write a function valueOr that takes an optional integer and a default value.

fn valueOr(x: ?i32, default: i32) i32 {
    return x orelse default;
}

Test:

std.debug.print("{d}\n", .{valueOr(10, 0)});
std.debug.print("{d}\n", .{valueOr(null, 0)});

Exercise 9-22. Write a function that returns a pointer to the first zero in a mutable slice.

fn firstZero(values: []i32) ?*i32 {
    for (values) |*v| {
        if (v.* == 0) {
            return v;
        }
    }

    return null;
}

Use the returned pointer to change the zero to 100.

var values = [_]i32{ 3, 2, 0, 8 };

if (firstZero(&values)) |p| {
    p.* = 100;
}

Exercise 9-23. Write a function that returns the last element of a slice.

fn last(values: []const i32) ?i32 {
    if (values.len == 0) {
        return null;
    }

    return values[values.len - 1];
}

Exercise 9-24. Decide whether each return type should be optional or error union.

find a user by id
open a socket
look up an environment variable
allocate memory
find a substring
parse a JSON document

Use optional for normal absence. Use an error union for failed operations.

Exercise 9-25. Combine errors and optionals.

Write a function declaration for loading a key from a config file.

The file operation may fail.

The key may be absent.

A suitable return type is:

fn loadConfigValue(path: []const u8, key: []const u8) !?[]const u8

The caller should handle it in two steps:

const value = try loadConfigValue("app.conf", "port");

if (value) |v| {
    std.debug.print("port = {s}\n", .{v});
} else {
    std.debug.print("port not set\n", .{});
}

Exercise 9-26. Write a linked list traversal using optional pointers.

const Node = struct {
    value: i32,
    next: ?*Node,
};

Start with:

var current: ?*Node = &head;

Loop until current becomes null.

while (current) |node| {
    std.debug.print("{d}\n", .{node.value});
    current = node.next;
}

Exercise 9-27. Explain why ?*T and *T are different types.

Exercise 9-28. Explain why ?usize is better than returning usize with a special value like 0 or maxInt(usize) for “not found.”