Guide 8 of 53
Conditions and error recovery
Conditions, collection entries, comparison chains and Result boundaries.
On this page
Conditions, collection entries, comparison chains and Result boundaries.
Conditions with bindings¶
Comma-separated conditions run lazily from left to right. Successful pattern bindings are visible in subsequent clauses and the success body. A guard introducing bindings must exit on every failure path.
Practice in By Example · Source program
// Feature: lazy comma conditions and binding guards.
// Each pattern publishes its bindings only after matching successfully.
enum Entry { Value(int), Missing }
fn positive(entry: Entry) -> int {
guard let .Value(value) = entry, value > 0 else { return 0 }
return value
}
print(positive(Entry.Value(7)))
print(positive(Entry.Value(-1)))
let value = 99
if let .Value(value) = Entry.Value(3), value > 10 {
print(value)
} else {
// Failure sees the outer binding, including failure of a later filter.
print(value)
}
fn next(index: int) -> int? {
if index <= 3 { return index }
return nil
}
let mut index = 1
while let item = next(index), item < 3 {
print(item)
index += 1
}
print(index)
// expected:
// 7
// 0
// 99
// 1
// 2
// 3Conditional array entries¶
Use if condition => value and for item in items => value inside an array. Entries are emitted eagerly in source order. False conditions omit entries; a nil value still occupies a slot. Nested arrays remain values unless explicitly spread.
Practice in By Example · Source program
// Mix ordinary values, conditional entries and loops in one array.
struct Page { title: str, enabled: bool }
let admin = true
let pages = [
Page { title: "Reports", enabled: true },
Page { title: "Drafts", enabled: false },
]
let menu = [
"Home",
if admin => "Admin",
for page in pages => if page.enabled => page.title,
]
for title in menu { print(title) }
// expected: Home
// expected: Admin
// expected: Reports
let positions = [for position, page in pages => position + 10]
print(positions[0])
// expected: 10
print(positions[1])
// expected: 11
let flattened = [0, for row in [[1, 2], [3]] => for cell in row => cell, 4]
print(flattened.len())
// expected: 5
let with_nil = [if true => nil, 7]
print(with_nil.len())
// expected: 2Local Result boundaries¶
attempt evaluates a block and wraps its normal tail in Ok. A failing Result question mark exits the nearest attempt as Err, so the caller can continue. A Result returned as the tail remains nested. Panics are not caught; optional propagation needs an explicit error conversion.
Practice in By Example · Source program
// Feature: local Result boundaries
// Syntax: attempt { let value = fallible()?; value }
// When to use: handle a Result failure locally and continue the caller.
use std::Result
fn read_number(text: str) -> Result<int, str> {
if text == "42" { return Result.Ok(42) }
return Result.Err("bad number")
}
fn main() {
let good: Result<int, str> = attempt {
let value = read_number("42")?
value + 1
}
match good {
Result::Ok(value) => print(value),
Result::Err(error) => print(error),
}
// expected: 43
let bad: Result<int, str> = attempt {
let value = read_number("other")?
value + 1
}
match bad {
Result::Ok(value) => print(value),
Result::Err(error) => print(error),
}
// expected: bad number
print("caller continues")
// expected: caller continues
}
main()Chained comparisons¶
Write lower <= value < upper as one comparison chain. Each shared operand is evaluated once, and a failed comparison stops the chain. This keeps interval checks readable without repeating a potentially expensive expression.
Practice in By Example · Source program
// Chained comparisons reuse the middle value and stop on the first false pair.
var calls = 0
fn read(value: int) -> int { calls += 1; value }
let age = 18
print(0 <= age < 150) // expected: true
print(1 < 2 <= 2) // expected: true
print(4 > 2 >= 2) // expected: true
print(0 < read(2) < 3) // expected: true
print(calls) // expected: 1
calls = 0
print(5 < read(2) < read(9)) // expected: false
print(calls) // expected: 1
print(1d < 2d <= 3d) // expected: true
print("a" < "b" < "c") // expected: trueExpression Result recovery¶
recover evaluates its Result once and unwraps Ok. Its patterns match the Err payload and must cover that payload type. Continuing branches return the same type as the successful value. Returns, propagation and cleanup still belong to the surrounding scope.
Practice in By Example · Source program
// A successful Result keeps its value. Recovery handles only its error.
enum ReadError { Missing, Invalid }
fn number(result: Result<int, ReadError>) -> int {
result recover {
.Missing => 0,
.Invalid => -1,
}
}
fn preserve(result: Result<int, str>) -> Result<int, str> {
let value = result recover { error => return Result.Err(error) }
Result.Ok(value)
}
fn main() {
print(number(Result.Ok(7)))
print(number(Result.Err(ReadError.Missing)))
print(number(Result.Err(ReadError.Invalid)))
print(preserve(Result.Err("missing")).is_err())
}Pattern bindings with is¶
Use is with a value pattern to test and bind in one condition. Captures are visible only along the successful path, including subsequent condition clauses. Existing is Type and is not Type tests keep their meanings.
Practice in By Example · Source program
enum Reading { Value(int), Missing }
fn read(input: Reading) -> int {
guard input is .Value(value) else { return 0 }
value
}
let input: Reading = .Value(7)
if input is .Value(value) && value > 0 {
assert(value == 7)
}
assert(read(input) == 7)
assert(read(Reading.Missing) == 0)
let values = [if input is .Value(value) => value]
let names = #{if input is .Value(value) => [value]: "selected"}
assert(values[0] == 7)
assert(names[7] == "selected")Filtering iteration with for case¶
for case pattern in values skips values that do not match, binding only the successful body. A plain refutable for loop retains its mismatch panic. The iterable is evaluated once and continue targets the filtering loop normally.
Practice in By Example · Source program
// Filtering loops evaluate their input once and skip non-matching elements.
var acquisitions = 0
fn results() -> [Result<int, str>] {
acquisitions += 1;
[.Ok(1), .Err("skip"), .Ok(2), .Ok(3)]
}
var total = 0
var events = ""
let __zolo_for_case_0 = 100
for case .Ok(value) in results() {
assert(__zolo_for_case_0 == 100)
defer { events += "{value}" }
if value == 2 { continue }
total += value
}
print(total) // expect: 4
print(acquisitions) // expect: 1
print(events) // expect: 123
let candidates: [Result<int,str>] = [.Ok(1), .Err("skip"), .Ok(2)]
var labels = 0
:outer for round in 0..3 {
for case .Ok(value) in candidates {
labels += 1
if round == 0 && value == 1 { continue :outer }
if round == 1 && value == 2 { break :outer }
}
}
print(labels) // expect: 3
var hits = 0
for case 1 | 3 in [1, 2, 3] { hits += 1 }
print(hits) // expect: 2
let optional: [int?] = [1, nil, 3]
var wildcards = 0
var nils = 0
for case _ in optional { wildcards += 1 }
for case nil in optional { nils += 1 }
print(wildcards) // expect: 3
print(nils) // expect: 1
let options: [Option<int>] = [.Some(8), .None]
for case whole @ .Some(value) in options {
let original: Option<int> = whole
print(value) // expect: 8
print(original.unwrap()) // expect: 8
}
// A matching body's linear resources still follow their own using scope.
@linear struct Resource { id: int }
impl core::cleanup::Cleanup for Resource {
fn close(self) { print("close"); print(self.id) }
}
let resources: [Result<int,str>] = [.Ok(4), .Err("skip"), .Ok(5)]
for case .Ok(value) in resources {
using resource = Resource { id: value } {
if value == 4 { continue }
break
}
}
// expect: close
// expect: 4
// expect: close
// expect: 5
// Numeric range bounds are frozen before the body and end without overflow.
let maximum = 9223372036854775807
let minimum = -maximum - 1
let start = 0
var starts = 0
for case value in start..({ start = 3; 4 }) { starts += 1 }
print(starts) // expect: 4
let end = 4
var ends = 0
for case value in 0..end { end = 0; ends += 1 }
print(ends) // expect: 4
var singleton = 0
for case value in maximum..=maximum { singleton += 1; continue }
print(singleton) // expect: 1
var empty = 0
for case value in minimum..minimum { empty += 1 }
print(empty) // expect: 0
var opened = 0
for case value in maximum.. { assert(value == maximum); opened += 1; break }
print(opened) // expect: 1
// Iterator return stays in the caller and closes its enclosing resource.
fn header_return() -> int {
let values: [Result<int, str>] = [.Ok(1)]
using resource = Resource { id: 9 } {
for case .Ok(item) in ({ return 7; values }) {}
}
99
}
print(header_return())
// expect: close
// expect: 9
// expect: 7
// Filtering strings uses Unicode scalar elements rather than UTF-8 bytes.
var unicode_hits = 0
var unicode_count = 0
var unicode_text = ""
for case "é" in "é🙂" { unicode_hits += 1 }
for case character in "é🙂" { unicode_count += 1; unicode_text += character }
print(unicode_hits)
print(unicode_count)
print(unicode_text)
macro count_elements(values) { var count = 0; for case _ in $values { count += 1 }; count }
print(count_elements!("é🙂"))
print(count_elements!([1, 2, 3]))Filtering collection builders¶
for case pattern in values => entry only emits an entry when the pattern matches. Failed matches do not run the entry expression. This form works in both array and map builders; pattern bindings are local to each matching entry.
// Failed readings produce neither an entry nor a body evaluation.
let readings: [Result<int, str>] = [.Ok(3), .Err("offline"), .Ok(5)]
var evaluated = 0
fn double(value: int) -> int { evaluated += 1; value * 2 }
let values = [for case .Ok(value) in readings => double(value)]
print(values[0]) // expect: 6
print(values[1]) // expect: 10
print(values.len()) // expect: 2
print(evaluated) // expect: 2
let indexed = #{for case .Ok(value) in readings => [value]: value * 2}
print(indexed[3]) // expect: 6
print(indexed[5]) // expect: 10Continue learning¶
DOCS / FEEDBACK
Did this page leave a question?
Tell us where the explanation lost you. Documentation is part of the language experience.