Loops as Expressions
const std = @import("std");
const expect = std.testing.expect;
fn firstIndexOf(haystack: []const u8, needle: u8) ?usize {
// The `else` branch runs only if the loop finished without breaking,
// so "not found" cannot be forgotten.
return for (haystack, 0..) |c, i| {
if (c == needle) break i;
} else null;
}
test "for as an expression" {
try expect(firstIndexOf("hello", 'l').? == 2);
try expect(firstIndexOf("hello", 'z') == null);
}
test "while as an expression" {
var i: u32 = 0;
const result = while (i < 10) : (i += 1) {
if (i == 4) break i * 10;
} else 0;
try expect(result == 40);
}
test "the else branch runs when nothing breaks" {
var i: u32 = 0;
const result = while (i < 3) : (i += 1) {
if (i == 99) break @as(u32, 1);
} else @as(u32, 2);
try expect(result == 2);
}A loop can produce a value: break x yields x, and the else branch supplies
the value when the loop finishes without ever breaking.
return for (haystack, 0..) |c, i| {
if (c == needle) break i;
} else null;
That is a complete linear search, and the “not found” case is structurally
impossible to forget: leave off the else and it does not compile.
Compare the usual alternative:
var result: ?usize = null;
for (haystack, 0..) |c, i| {
if (c == needle) { result = i; break; }
}
return result;
Same behaviour, but now result is mutable, lives longer than it needs to, and
nothing forces you to initialise it correctly.