Mojo compound statements reference
A compound statement has a header and a body. The header ends
with : and is followed by an indented block with the body.
The body can contain simple statements, other compound statements, or both.
if condition: # Header
do_something() # BodyThe body must be indented more than the header. The first body statement sets the indentation for the rest of the body:
if condition:
do_something()
do_more() # Error: statement has excess indentationIf statements
An if statement executes a block conditionally:
if x > 0:
print("positive")
elif x < 0:
print("negative")
elif x == 0:
print("zero")
else:
print("you should never get here")Conditions are evaluated in order. Add as many as needed.
The first true condition runs its block, and the statement exits.
The else block runs if no condition is true.
When the body is a single simple statement, you can write it on a single line, although this is not normally recommended in many house styles:
if x > 0: print("positive")Common shortcuts from other languages won't work in Mojo:
x > 0 and print("positive") # Error: 'None' does not implement
# the '__bool__' method
print("positive") if x > 0 else pass # Error: unexpected token in expressionWhile loops
The while loop repeats its body while a condition is true:
var count = 0
while count < 10:
print(count)
count += 1Use break to exit the loop early and continue to skip to the next
iteration:
while True:
var item = get_next()
if item is None:
break # Exit loop if no more items
if not is_valid(item):
continue # Skip invalid items
process(item) # Only runs for valid itemsFor loops
The for statement iterates over a sequence:
for item in items:
process(item)
for i in range(10): # [0, 10)
print(i)To support iteration, a sequence must implement __iter__() and
__next__(). A for loop desugars to a while loop that uses these
methods.
Destructuring works directly in the loop target. This lets you unpack tuple
elements as you iterate. In this example, each item in pairs is unpacked
into key and value for every iteration:
for key, value in pairs: # For example, [("a", 1), ("b", 2), ...]
print(key, value)Loops and else clauses
An optional else clause runs when the loop exits normally,
when the condition becomes false. It does not run if the loop
exits with break:
var found = False
for item in items:
if item == target:
found = True
break
else:
print("not found") # Only runs if break was never hitBoth for and while loops support else.
Error handling
A try statement executes code that may raise errors.
try:
result = risky()
except e:
handle(e)Structure
Each try statement requires at least one except or finally clause:
try:
operation()
except e:
handle_error(e) # Runs if an error occurs
else:
on_success() # Runs only if no error occurred
finally:
cleanup() # Always runsExecution proceeds in a fixed order:
- The
tryblock runs first. - If an error occurs, the matching
exceptblock runs. - If no error occurs, the
elseblock (if present) runs after thetryblock. - If included, a
finallyblock always runs last.
Error binding
Bind the error to a name with except name:
try:
risky()
except e:
print(e) # e is the caught errorWithout a binding, the error is caught but not accessible. There is no default error variable. This is useful when you want to respond to an error state without needing the error details:
try:
risky()
except:
print("something went wrong")Typed errors
When a function declares a specific error type with raises ErrorType,
the bound variable's type is inferred:
@fieldwise_init
struct NetworkError:
var message: String
var code: Int
def fetch() raises NetworkError -> String:
raise NetworkError("HTCPCP", 418)
try:
result = fetch()
except e: # e is inferred as `NetworkError`
print(e.message) # Known types supports direct field access
print(e.code)A try block handles one error type. The compiler raises an
error if code in the try block can raise more than one
error type.
Context managers
A with statement manages resources using context managers. Context
managers define setup (__enter__) and cleanup (__exit__) operations.
The cleanup always runs when the block exits, even if an error occurs:
with open("file.txt") as f:
content = f.read()
# File is closed here, even if an error occurredMultiple context managers can share a single with statement:
with open("input.txt") as f_in, open("output.txt", "w") as f_out:
f_out.write(f_in.read())This is equivalent to nested with statements.
How context managers work
When a with block is entered, __enter__() is called on the context
manager expression. The result is bound to the as target if present.
When the block exits, __exit__() is called. A context manager that
defines only __enter__() is valid — __exit__() is optional.
Compile-time control flow
comptime if and comptime for run at compile time. The condition
or sequence must be a compile-time value or expression.
Use them to generate code based on compile-time conditions. You
cannot use runtime values in comptime statements.
comptime if
comptime if selects a branch at compile time, pruning the unselected
branches. Only the selected branch appears in the compiled program.
from std.sys import size_of
comptime if size_of[Int]() == 8:
print("64-bit")
else:
print("Probably 32-bit")The condition must be a compile-time expression. In this example,
runtime_value is not available at compile time, so the code errors
during compilation:
comptime if runtime_value > 0: # Error: 'comptime if' requires a
pass # parameter expression as a conditioncomptime if supports elif and else like the regular if statement.
comptime for
comptime for unrolls a loop at compile time. Each iteration is
compiled as separate code. This creates a bigger binary but improves
runtime performance by eliminating loop overhead and enabling further
optimizations.
comptime for i in range(3):
print(i) # Compiled as: print(0); print(1); print(2)Use comptime for to generate repeated code patterns or iterate over
compile-time sequences.
Scopes
Each compound statement body creates a new scope. Variables declared inside a body are not visible outside it:
if condition:
var x = 10
print(x) # Error: x is not in scopewith statement variables bound with as are scoped to the with block:
with open("file.txt") as f:
data = f.read()
# f is not accessible hereNested functions create their own scope and can capture variables from
enclosing functions with the capturing keyword:
def outer():
count = 0
def inner() capturing:
count += 1 # Captures count from outer
inner()
print(count) # 1Was this page helpful?
Thank you! We'll create more content like this.
Thank you for helping us improve!