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 documentUse 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 u8The 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.”