> For the complete Mojo documentation index, see [llms.txt](/llms.txt).
> Markdown versions of all pages are available by appending .md to any URL (e.g. /docs/manual/basics.md).

# Mojo compound statements reference

<!-- VERIFIED: ParserStmts.cpp parseIfStmt(), parseWhileStmt(),
     parseForStmt(), parseTryStmt(), parseWithStmt(),
     parseComptimeCompoundStmt(), parseParamIf(), parseParamFor(),
     parseSuite() -->

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.

```mojo
if condition:       # Header
    do_something()  # Body
```

The body must be indented more than the header. The first body statement
sets the indentation for the rest of the body:

```mojo
if condition:
    do_something()
      do_more()    # Error: statement has excess indentation
```

## If statements

An `if` statement executes a block conditionally:

```mojo
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:

```mojo
if x > 0: print("positive")
```

Common shortcuts from other languages won't work in Mojo:

```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 expression
```

## While loops

The `while` loop repeats its body while a condition is true:

```mojo
var count = 0
while count < 10:
    print(count)
    count += 1
```

Use `break` to exit the loop early and `continue` to skip to the next
iteration:

```mojo
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 items
```

## For loops

The `for` statement iterates over a sequence:

```mojo
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:

```mojo
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`:

```mojo
var found = False
for item in items:
    if item == target:
        found = True
        break
else:
    print("not found")  # Only runs if break was never hit
```

Both `for` and `while` loops support `else`.

## Error handling

A `try` statement executes code that may raise errors.

```mojo
try:
    result = risky()
except e:
    handle(e)
```

### Structure

Each `try` statement requires at least one `except` or `finally` clause:

```mojo
try:
    operation()
except e:
    handle_error(e)   # Runs if an error occurs
else:
    on_success()      # Runs only if no error occurred
finally:
    cleanup()         # Always runs
```

Execution proceeds in a fixed order:

1. The `try` block runs first.
1. If an error occurs, the matching `except` block runs.
1. If no error occurs, the `else` block (if present) runs after the `try`
   block.
1. If included, a `finally` block always runs last.

### Error binding

Bind the error to a name with `except name`:

```mojo
try:
    risky()
except e:
    print(e)   # e is the caught error
```

Without 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:

```mojo
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:

```mojo
@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:

```mojo
with open("file.txt") as f:
    content = f.read()
# File is closed here, even if an error occurred
```

Multiple context managers can share a single `with` statement:

```mojo
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.

```mojo
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:

```mojo
comptime if runtime_value > 0:   # Error: 'comptime if' requires a
    pass                         # parameter expression as a condition
```

`comptime 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.

```mojo
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:

```mojo
if condition:
    var x = 10
print(x)   # Error: x is not in scope
```

`with` statement variables bound with `as` are scoped to the `with` block:

```mojo
with open("file.txt") as f:
    data = f.read()
# f is not accessible here
```

Nested functions create their own scope and can capture variables from
enclosing functions with the `capturing` keyword:

```mojo
def outer():
    count = 0

    def inner() capturing:
        count += 1   # Captures count from outer

    inner()
    print(count)     # 1
```
