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 1s and 0s inside a number, which is different from the logical and keyword that works on true/false values. 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 i in 1..6 {
t += i;
}
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 i in rev(1..=5) {
t = t * 10 + i;
}
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");
}