Skip to content

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
// 3

Conditional 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: 2

Local 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: true

Expression 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: 10

Continue learning

Documentation · Practice in By Example

DOCS / FEEDBACK

Did this page leave a question?

Tell us where the explanation lost you. Documentation is part of the language experience.

Global index

Find your way through Zolo

Try an idea

Start here

9 results

9 results

enespt-br