Table of Contents

Conditionals and loops

Raven uses familiar conditionals and loops, with expression forms where a construct needs to produce a value. Blocks introduce a local scope and evaluate to their final expression, or to () when they have no final value.

let next = {
    let current = 10
    current + 1
}

When a block is used as a statement body, any final value is discarded.

Conditional expressions

An if expression evaluates one branch and produces that branch's value. Branches may be blocks or single expressions:

let label = if score >= 50 then "pass" else "fail"

let adjusted = if score > limit {
    limit
} else {
    score
}

then is an optional contextual keyword that explicitly separates the condition from the successful branch. It is primarily intended for value-producing conditionals with expression branches, especially when the header and branch begin on the same line, but it may also precede a line-broken branch or a block:

let label = if score >= 50 then
    "pass"
else
    "fail"

let clamped = if score > limit then {
    limit
} else {
    score
}

Because then is contextual rather than reserved, it remains available as an identifier outside this position.

When the result is used, an else branch is required and the branch values must have a compatible type. An if used only for its effects may omit else.

An if let header tests a pattern and introduces its bindings in the successful branch:

let value = if let Some(number) = option {
    number
} else {
    0
}

The value on the right is evaluated once. The leading let, val, or var sets the binding mode for bare captures and an optional whole-pattern designation. The bindings are not visible in the else branch. See Pattern matching for the complete pattern rules.

while

while repeats its body while its condition is true:

var index = 0
while index < items.Length {
    Console.WriteLine(items[index])
    index += 1
}

A while true loop has no reachable endpoint unless its body contains a reachable break. Other conditions are treated conservatively because they may be false before the first iteration.

while let evaluates and matches a value before each iteration. A successful match runs the body with the pattern bindings in scope; a failed match ends the loop.

while let Ok(value) = next() {
    Console.WriteLine(value)
}

As with if let, the leading binding keyword is required and controls bare captures and an optional whole-pattern designation.

loop

loop repeats indefinitely until a break, return, throw, or another abrupt transfer leaves it.

var attempts = 0

loop {
    attempts += 1
    if attempts == 3 {
        break
    }
}

for

for evaluates its collection once and visits each element. The target may be a local name, a discard, an omitted target, or a pattern.

for item in items {
    Console.WriteLine(item)
}

for _ in items {
    recordVisit()
}

for in items {
    recordVisit()
}

A simple name gets its type from the collection's element type. Raven supports arrays, IEnumerable<T>, the enumerator pattern through Current, and non-generic enumeration with object elements. let and val are optional for a simple target; var is not allowed. An explicit annotation constrains the element type:

for item: int in values {
    Console.WriteLine(item)
}

The collection's element type must be implicitly assignable to the annotation.

Pattern targets

A pattern target runs the body only for matching elements. Non-matching elements are skipped.

for (let x, 0) in points {
    Console.WriteLine(x)
}

for let Person(1, name, _) person in people {
    Console.WriteLine(person.Name)
    Console.WriteLine(name)
}

An outer let, val, or var supplies the binding mode for otherwise bare captures and for an optional whole-pattern designation such as person. Do not combine that outer mode with inline binding keywords in the same target.

Range loops

A for target can iterate over an explicit or from-start range of integral, floating-point, char, or decimal values. .. includes the end; ..< excludes it. An omitted start means 0. The end may not be omitted or use a from-end bound.

for x in 0..10 { }
for x in ..<10 { }
for x in 0..<10 by 2 { }
for x in 10..0 by -3 { }
for x in 0..10.0 by 0.1 { }

The optional by step must be non-zero and is only valid for a range loop. A positive step proceeds toward a larger end; a negative step proceeds toward a smaller end. Inclusive ranges compare with <= or >=, while exclusive ranges use < or >.

Range-loop diagnostics identify each invalid shape: a missing end reports RAV2602, a from-end bound reports RAV2603, a zero step reports RAV2604, and a by clause on a non-range collection reports RAV2605.

Async enumeration

Use await for in an async context to enumerate a value that provides GetAsyncEnumerator, MoveNextAsync, and Current:

async func process(values: IAsyncEnumerable<int>) -> Task {
    await for value in values {
        Console.WriteLine(value)
    }
}

Detailed flow rules

Use break and continue to control loop execution, optionally targeting an enclosing labeled loop. See Jumps and labels. Use Return and yield for callable and iterator exits, and Exceptions and structured handling for throw, try, and typed failures.

Statements after an unconditional transfer are unreachable. Raven reports RAV0162 on each unreachable statement so dead paths remain visible.