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