Keywords

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");
}