Guards and loops
Rat has no if/else keyword. Conditions are written as parenthesized guards followed by an action: (cond) action. Adjacent guards form a first-match chain. (else) closes one. Loops use [item] in coll on the body line.
One guard, one action
No else needed when not used
A lone guard runs its action only when the condition is true and falls through otherwise. Use it for inline gates: render an admin link only when authed, show an error only when one exists.
> server
score: 78
> page
<p> score: [score]
(score >= 50)
<p class['pass']> passing score: 78
passing
First-match chain
Sibling guards short-circuit at the first hit
Stacked guards at the same indent form a chain; only the first matching branch runs. Use (else) as the final catch-all when one is required.
> page
(score >= 90)
<p> excellent
(score >= 50)
<p> passing
(else)
<p> needs work Two independent decisions
A statement between guards starts a new chain
Chaining is positional: a chain ends at the first non-guard line, and a blank line is NOT a separator, so two guard groups with only whitespace between them are still one chain. When two conditions are independent questions rather than branches of one decision, put a real statement between them. In markup any tag works; here the <hr> keeps the score ladder and the lucky-number check separate. Without it, all four branches fuse into one chain and a passing score would silently skip the second question.
> page
(score >= 50)
<p> passing
(else)
<p> needs work
<hr>
(score == 78)
<p> the lucky demo number passing
the lucky demo number
Chains in function bodies
A matching branch that does not return still consumes the chain
The same first-match rule drives function logic, and it is where the chain can surprise you: when an early branch matches and merely mutates without returning, the rest of the chain is still skipped. Below, the team-normalization pair and the capacity check are independent questions. The [count] line between them starts a new chain; written adjacent, a coral join would match the first branch and the capacity check would never run. As in markup, a blank line alone does not break the chain.
> join[team, seats]
(team == 'coral')
team << 'a'
(team == 'teal')
team << 'b'
[count] << len(seats)
(count >= 8)
<< 'room is full'
<< 'joined crew ' + team Loop with in coll
Iterate over arrays
Place the binder on the body line just like a tag. The framework rebinds the loop variable on each pass, so any handlers attached to the elements capture the right value. Empty collections render nothing.
Loops over page-tier state are reactive: a flat name ([item] in items) and a dotted path rooted at one ([e] in board.entries) both re-render their rows in place when a handler mutates the collection, with no server round-trip. Loops over server-tier collections render once on the server, dotted or not.
> server
items: ['apples', 'pears', 'figs']
> page
<ul>
[item] in items
<li> [item] - apples
- pears
- figs
Guards inside a loop
Compose freely
Guards and loops nest in either direction. Filter the rendered output inline rather than precomputing a separate array.
> page
<ul>
[item] in items
(len(item) >= 5)
<li> [item] - apples
- pears
break / continue
Loop control inside function and handler bodies
In a function or handler body, break stops the innermost loop and continue skips to the next iteration. Use them as a guard action ((cond) break) or on their own line. They unwind only the nearest loop, so an inner break leaves an outer loop running. They also work in a static markup loop ([x] in coll over a server-tier or local collection), stopping or skipping the emitted elements. A page-tier (reactive) loop re-renders its whole collection on the client, so loop control there is left to the SSR pass.
> first_big[xs, limit]
[i] in len(xs)
(xs.at(i) <= limit) continue
<< xs.at(i)
<< -1
> count_until_zero[xs]
n << 0
[i] in len(xs)
(xs.at(i) == 0) break
n << n + 1
<< n while loops
Condition-driven, with an optional pass counter
When you don't know the trip count up front - a convergence loop, a queue that drains, a retry - use while (cond). The condition reads as prose, like a guard, and is re-checked each pass. The gcd below also shows a destructuring swap: [a, b] << [b, a % b] updates both names at once. Add an optional bracket counter, while (cond), and i is a free, read-only pass index (0, 1, 2, …) usable in the body for an indexing or "give up after N" guard; the loop still exits on the condition, never on i. Every loop is bounded by a hard iteration ceiling, so a runaway while (true) fails loudly instead of hanging.
> gcd[a, b]
while (b > 0)
[a, b] << [b, a % b]
<< a
> collatz_steps[n]
x << n
steps << 0
while (x > 1)
(x % 2 == 0) x << x / 2
(x % 2 == 1) x << 3 * x + 1
steps << steps + 1
<< steps Infinite loops and ranges
in inf, and the a..b shorthand
inf is a real value (positive infinity), so in inf is the infinite loop - iterate i = 0, 1, 2, … and leave with break or a return. inf also seeds a min-search (best << inf, since every value is below it); -inf does the max. For a plain numeric span, a..b is shorthand for range(a, b) - in 2..20.
> first_factor[n]
[d] in inf
(d < 2) continue
(n % d == 0) << d
<< n
> smallest[xs]
best << inf
[x] in xs
(x < best) best << x
<< best See also Functions · Reactivity · Collections