IMPORTANT: To view this page as Markdown, append `.md` to the URL (e.g. /docs/manual/basics.md). For the complete Mojo documentation index, see llms.txt.
Skip to main content
Version: Nightly
For the complete Mojo documentation index, see llms.txt. Markdown versions of all pages are available by appending .md to any URL (e.g. /docs/manual/basics.md).

Mojo simple statements reference

A simple statement performs a single action on one logical line. Multiple simple statements can share a line when separated by semicolons.

Import statements

Import statements expose modules and their members to the current scope. Imports can appear at module level, inside functions, or inside other scopes. They don't need to appear at the top of a file.

import std.math
from std.collections import Dict, Set

Use parentheses to import on multiple lines for readability and support clean commit diffs:

from std.collections import (
Dict,
Set,
List, # Trailing comma is legal
)

Module imports

import std.math
import numpy as np # Alias the module name to avoid collisions

Selective imports

from std.math import sqrt, pi
from std.collections import Dict as Dictionary # Alias the imported name

Wildcard imports

from std.math import * # Imports all public names from the std.math module

Expression statements

An expression statement evaluates an expression for its side effects. When the result is unused (other than None), the compiler warns:

x + y # Warning: result is unused

The compiler doesn't warn when the result is None, which is common for functions called for their side effects:

print("hello") # Side effect: prints
trigger() # Side effect: called for behavior

Assign the result to _ to explicitly discard it and silence the warning:

_ = update() # Explicitly discard the result

Expressions are not valid at module scope or in struct bodies outside of methods.

Assignment statements

Assignment statements bind values to names with =. Use var declarations to declare new variables:

var x = 42
var name = "Alice"
var result = compute()

Use ref declarations to create reference bindings:

ref y = my_list[3] # `y` is a reference to the value at `my_list[3]`

The y reference binding does not create a new value. It creates a reference to the existing value at my_list[3]. Modifying y modifies the value in my_list[3]; modifying my_list[3] modifies what y reads.

Annotated assignments bind a type to a name, with an optional initializer. Type annotations aren't required, but they improve readability and catch errors:

x: Int = 42
name: String = "Alice"
values: List[Float64] = []

A var without a type and without an initializer is an error:

var x # Error: declaration must have either a type or an initializer
var x: Int # OK: type provided, value uninitialized
var x = 42 # OK: type inferred from initializer

When types are complex and long to write, you can use comptime aliases to keep your code concise:

comptime Vec3 = List[Float64]
var position: Vec3 = [0.0, 0.0, 0.0]

Multiple assignment

Multiple assignments give you a concise syntax for initializing variables.

Assign the same value to multiple names. This is right-associative. z is assigned first, then y, then x. If the RHS has side effects, they run once:

# Good for initializing counters/flags to the same literal.
var x = y = z = "Hello"
print(x, y, z) # Hello Hello Hello

# Mixing conventions
ref a = var b = var c = "Hello"
print(a, b, c)

a = "World"
print(b) # World

Destructuring assignment:

var a, b = 1, 2 # Destructuring assignment
var (c, d) = (1, 2) # Equivalent destructuring
# not "assign tuple to tuple"
print(a, b, c, d) # 1 2 1 2

def returns_pair() -> Tuple[Int, Int]:
return (1, 2)

var e, f = returns_pair()
print(e, f) # 1 2

Avoid multiple assignments for destructuring unrelated values.

var temperature, name = 98.6, "Bob"

reads worse than two lines:

var temperature = 98.6
var name = "Bob"

Simple swaps

var a, b, c = 1, 2, 3
a, b, c = c, a, b
print(a, b, c) # 3 1 2

Augmented assignment

Augmented assignment is syntactic sugar that combines an operation with assignment. The left-hand side is evaluated once:

x += 5 # x = x + 5
x -= 2 # x = x - 2
x *= 3 # x = x * 3
x /= 4 # x = x / 4
x //= 2 # x = x // 2
x %= 7 # x = x % 7
x **= 2 # x = x ** 2
x @= m # x = x @ m (matrix multiply)
x &= mask # x = x & mask
x |= flags # x = x | flags
x ^= bits # x = x ^ bits
x <<= 1 # x = x << 1
x >>= 1 # x = x >> 1

The pass statement

pass is a no-op. Use it as a placeholder where a statement is required but no action is needed:

def not_ready():
pass

struct Empty:
pass

pass is required in empty function and struct bodies to avoid syntax errors.

The return statement

return exits a function and optionally returns a value:

def greet(name: String):
if not name: # String is falsy when empty
return
print(t"Hello, {name}!")

def get_value() -> Int:
return 42

def early_exit(items: List[Int], target: Int) -> Bool:
for item in items:
if item == target:
return True
return False

A function without an explicit return implicitly returns None. return is only valid inside a function:

return 42 # Error: cannot return from this context

The raise statement

raise raises an error. The function must be declared with raises or included within a try block:

def validate(value: Int) raises -> Bool:
if value < 0:
raise Error("value must be non-negative")
return True

def mitigate_risk():
try:
if not perform_some_test():
raise Error("test failed")
# perform risky work, knowing test passed
except e:
log(e)

To propagate errors from a try block, use a bare raise to re-raise the current error:

try:
validate(value)
# perform work, knowing value is valid
except e:
raise # Re-raises current error to the next handler

Raising outside a valid context is an error:

raise Error("oops") # Error: cannot raise error in this context
# (surround with try, or mark function as raises)

raise # Error: no contextual error to reraise
# (bare raise requires an active except block)

Control flow with break and continue statements

break exits the innermost loop immediately. continue skips to the next iteration:

for x in range(10):
if x == 5:
break # Stop at 5
if x % 2 == 0:
continue # Skip even numbers
print(x) # Prints 1, 3

Compile-time declarations

comptime declares a compile-time constant. The value must be computable at compile time:

comptime SIZE = 256
comptime MAX = SIZE * 2

comptime is also used to declare associated types in traits, and can be used to create type aliases and trait composition aliases:

comptime Permissive = ImplicitlyCopyable & ImplicitlyDestructible

trait SimpleTrait(Writable): # `SimpleTrait` refines `Writable`
comptime Element = Permissive # Associated type with a comptime alias