This page covers the building blocks that shape how your program makes decisions and repeats work: conditions, loops, breaks, and a few loop helpers that make common patterns shorter. Every example is verified with an assert so you can see the exact expected result.
fn main() {
Conditionals
'if' runs a block only when its condition is true. If the condition is false the block is skipped entirely — nothing else happens. 'panic' stops the program immediately with a message. During development it is a clear way to mark situations that should never occur.
if 2 > 5 {
panic("Incorrect test");
}
Combine conditions with 'and' / 'or' (or their symbol equivalents '&&' / '||'). Note that '&' is the bitwise AND operator — it works on the individual bits of a number, which is different from the logical 'and' keyword. An 'if/else' chain picks exactly one branch to execute.
a = 12;
if a > 10 and a & 7 == 4 {
a += 1;
} else {
a -= 1;
}
Text enclosed in double quotes can contain expressions inside curly braces — the expression is evaluated and its result is inserted into the text. 'assert' checks that its first argument is true; if it is false the second argument is printed as an error message.
assert(a == 13, "Incorrect value {a} != 13");
if as an expression
Every block in Loft produces a value — the last expression inside it. That means you can use 'if/else' on the right-hand side of an assignment, picking between two values based on a condition. No ternary operator needed.
b = if a == 13 {
"Correct"
} else {
"Wrong"
};
assert(b == "Correct", "Logic expression");
Iteration
'for x in a..b' loops over the integers starting at a and stopping before b (exclusive upper bound). Use '..=' to include the upper bound. Here we sum 1 + 2 + 3 + 4 + 5 = 15.
t = 0;
for a in 1..6 {
t += a;
}
assert(t == 15, "Total was {t} instead of 15");
'rev(range)' reverses the direction of any range. '1..=5' is inclusive, so it visits 5, 4, 3, 2, 1 in that order. Multiplying each digit into a running total builds the number 54321.
t = 0;
for a in rev(1..=5) {
t = t * 10 + a;
}
assert(t == 54321, "Result was {t} instead of 54321");
Nested loops and break
Loft has no 'while' or 'loop' keyword — all repetition uses 'for' with a range, so there is always a clear upper bound on how many iterations can occur. 'break' exits the nearest enclosing loop immediately. To exit an outer loop from inside an inner one, write 'outerVar#break'. Here x#break leaves both loops when the product x*y reaches 16.
b = "";
for x in 1..5 {
for y in 1..5 {
if y > x {
break;
this breaks the inner y loop
}
if x * y >= 16 {
x# break;
}
if len(b) > 0 {
b += "; ";
}
b += "{x}:{y}";
}
}
assert(b == "1:1; 2:1; 2:2; 3:1; 3:2; 3:3; 4:1; 4:2; 4:3", "Incorrect sequence '{b}'");
Loop helpers: #first and #count
Inside a loop body you have access to some useful metadata about the current iteration. 'x#first' is true only for the very first element that passes the filter. 'x#count' is a zero-based index counting only the elements that were not filtered out. An 'if' clause directly after 'in range' filters which values enter the loop — here we skip every value where x % 3 == 1, keeping 2, 3, 5, 6, 8, 9.
b = "";
for x in 1..=9 if x % 3 != 1 {
if !x#first {
b += ", ";
}
b += "{x#count}:{x}";
}
assert(b == "0:2, 1:3, 2:5, 3:6, 4:8, 5:9", "Sequence '{b}'");
Formatting a loop inline
A for loop can appear inside a format string. The results are collected into a bracketed, comma-separated list. A format specifier after the closing brace is applied to every element — ':02' means at least 2 digits, zero-padded. This is handy for building compact representations on the fly.
assert("a{for x in 1..7 {x*2}:02}b" == "a[02,04,06,08,10,12]b", "Format range");
}