Syntax
Control Flow and Loops
Kagari’s control flow is Rust’s structured control flow: if, if let, match, loop, while,
while let, for, break, continue and return, with labels on loops. There is no goto.
What a loop may do depends on the kind of the function it is in; the
last section lists the rules.
if and if let
if AFFECTION_MIO >= 3 { MioSmiles();} else if AFFECTION_MIO == 0 { MioIgnores();} else { MioShrugs();}
let label = if cleared { "Clear" } else { "New" };- The condition is a
boolexpression (E2001otherwise), written without parentheses. The branches are blocks; braces are required. - With an
else,ifis an expression whose value is the value of the branch taken; both branches have one type. Withoutelse, its type is(), and the block must have type(). - A struct expression in the condition must be parenthesized; see Expressions.
if let pattern = value { … } else { … } runs the first block when the pattern matches, with the
pattern’s bindings in scope there. The pattern may be refutable. An if let cannot be combined
with other conditions by &&; nest a second if instead.
if let Some(slot) = last_slot { show_slot(slot);}match
match choice!{ id = "confess", t!["Tell her the truth"] => Confess::Yes, t!["Say nothing"] => Confess::No,} { Confess::Yes => AzusaConfession(), Confess::No => s!(t!["I kept quiet."]),}match value { pattern => expression, … } runs the first arm whose pattern matches and whose
guard (pattern if condition => …) is true. Its value is the value of that arm; all arms have one
type. A match must be exhaustive (E2037). Patterns, guards and exhaustiveness are in
Patterns.
loop
let mut n = 0;let first_big = loop { n += 1; if n * n > 50 { break n; }};loop { … } repeats its body until a break leaves it. break value makes value the value of
the loop; all breaks of one loop give values of one type, and a plain break gives (). A
loop without any break never ends; its type is !, so it fits wherever a value of any type is
expected, such as the end of a scene that returns an ending.
while and while let
while count < 10 { count += 1;}
while let Some(next) = queue.get(index) { index += 1;}while condition { … } runs the body as long as the condition is true. while let pattern = value { … } runs it as long as the pattern matches. Both have type (); break inside them takes no
value (E2001).
for loops
for day in 0..7 { … }for i in 1..=3 { … }for name in ["Mio", "Azusa", "Hanako"] { … }for score in scores { … }for pattern in iterable { … } runs the body once per item. The pattern must be irrefutable
(E2038). The loop has type (), and break inside it takes no value.
| Iterable | Items | Order |
|---|---|---|
[T; N] | The N elements, of type T | First to last |
Vec<T> | The elements, of type T | First to last |
a..b, a..=b: a Range<T> of a Rust integer or Int type | The integers from a to b (excluded or included) | Ascending |
Any other type is E2031, including references to arrays. A range over anything but integers
(for example Decimal) is E2031 as well.
When the range is written in the for itself, the loop variable’s type covers exactly its
values: day in for day in 0..7 is an Int<0, 6>, i in for i in 1..=3 is an Int<1, 3>,
and with a and b of type Int<0, 9>, k in for k in a..b is an Int<0, 8>. A range value
of type Range<Int<lo, hi>> from elsewhere, such as a parameter, gives an Int<lo, hi>.
Labels, break and continue
'outer: for row in 0..4 { for col in 0..4 { if blocked(row, col) { continue 'outer; } if done(row, col) { break 'outer; } }}| Expression | Effect |
|---|---|
break | Leaves the innermost loop |
break value | Leaves the innermost loop with a value |
break 'label, break 'label value | Leaves the loop labelled 'label |
continue | Starts the next iteration of the innermost loop; in a for loop, with the next item |
continue 'label | Starts the next iteration of the loop labelled 'label |
A label is 'name: written before loop, while or for. Labels exist only on loops: a label on
a block or any other expression is a syntax error (E0001). A label that no enclosing loop
declares is E1019; break or continue outside a loop is E1020. break and continue have
type !.
return
return value leaves the function with value; return alone returns (). It has type !.
Without return, a function returns the value of its body block. A function with a return type
must produce a value of that type on every path; a path that reaches the end of the body without
one is a type error (E2001), and so is return; in a function that returns a value.
Rules by function kind
The constructs above are available everywhere, with these restrictions:
| Construct | fn | const fn | Scene | Instant procedure, closure in the story |
|---|---|---|---|---|
if, if let, match | yes | yes | yes | yes |
loop, while, while let | yes | yes | yes, if every path around the loop passes a step (E5001) | no (E3004) |
for over an array | yes | yes | yes | yes |
for over a range | yes | yes | bounds must be constant expressions (E3006) | bounds must be constant expressions (E3006) |
for over a Vec<T> | yes | yes | only a constant one (E3006) | only a constant one (E3006) |
| Recursion | yes | yes | only through a step (E5002) | no (E3005) |
In a pushdown fn, a for loop may iterate over any array, over a range whose two bounds are
constant expressions, or over any other constant expression. A Vec<T> is not a
finite type, so the story never holds one at runtime.
When a const fn is evaluated at compile time, the evaluation is limited to 1 000 000 steps
(E4003), so a loop that does not end there is reported instead of hanging the build.
Termination in the story
Everything the story does between two steps runs instantly, without input from the player, so it must always finish. The language makes this checkable:
- An instant procedure (a
pushdown fnwithout#[scene]) and a closure in the story have no unbounded loop and no recursion: nolooporwhile(E3004),foronly over arrays and constant ranges (E3006), and no cycle of calls among instant procedures (E3005). So every call of one finishes. - In a scene,
loopandwhileare allowed, but every path around the loop body must pass a step (E5001), and a scene may call itself, directly or through other scenes, only after a step (E5002). A call of a scene that may return without passing a step counts as stepless.
static mut READY: bool = false;
#[scene]pushdown fn Wait() { loop { if READY { s!(t!["Ready."]); } // E5001: when READY is false, the loop goes around without a step }}A hub that waits for the player on every lap is accepted:
enum Map { A, Leave }
#[scene]pushdown fn FestivalHub() { loop { match choice!{ id = "map", t!["Stall A"] => Map::A, t!["Leave"] => Map::Leave, } { Map::A => StallA(), Map::Leave => break, } }}The check counts every branch as possible; it does not try to prove that a stepless path is never
taken. A for loop in a scene over more than 1 048 576 items makes the story analysis give up
(W5007). The full rules are in
Scenes and Instant Procedures.
No goto
There is no goto and no label that marks a position in the code. A hub is a loop, a scene that
ends early returns, and a branch is an if or a match. Control can never jump into another
function or into the middle of a block or loop, so calls and returns always nest, which the story
analysis relies on.