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: 1.0.0b2
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 expression reference

An expression is any piece of code that produces a value. Expressions are the building blocks of computation: you combine them with operators, pass them as arguments, assign their results to variables, and use them as conditions in control flow.

Identifier expressions

An identifier refers to a named element: a variable, function, type, or module. Using an identifier in an expression gives you the thing it refers to:

score # a variable
Int # a type
range # a function

Parenthesized expressions

Parentheses group subexpressions, overriding default precedence:

(a + b) * c # add a and b, multiply by c
(x) # just x

Parentheses also let expressions span multiple lines without using backslash escapes:

var result = (
first_value
+ second_value
+ third_value
)

Tuples

A tuple is a fixed-size, ordered group of values. Commas create tuples, not parentheses:

var a = 2, 3 # tuple without parentheses
var b = (2, 3) # same tuple with parentheses
var x, y = b # x is 2, y is 3

Use a trailing comma to create a one-element tuple. Without it, (1) is just the integer 1 in parentheses:

() # empty tuple
(1,) # one-element tuple
(1, 2, 3) # three-element tuple

Tuples support indexing:

var point = (10, 20)
print(point[0]) # 10

Collection displays

The compiler calls these displays. Displays are similar to literals, but unlike literals, displays can contain expressions as well as fixed values. Literals cannot contain expressions.

For example:

[1, 2, 3] # list literal
[1, 1+1, 1+1+1] # list display

Lists

A list display creates a list from comma-separated values:

var empty: List[Float32] = []
var numbers = [1, 2, 3]
var strings = ["one", "two", "three",]

Mojo allows trailing commas after all collection elements, including the final one.

Dictionaries

A dict display maps keys to values with : between each pair:

var empty: Dict[String, Int] = {}
var ages = {"Alice": 30, "Bob": 25}

Sets

A set display uses braces with values but no colons:

var primes = {2, 3, 5, 7}

Don't mix set and dict syntax. {1, 2} is a set. {1: 2} is a dict.

{"a": 1, 2} # Error: expected 'key: value' in dictionary expression
{1, "b": 2} # Error: cannot have a 'key: value' pair in set initializer

Sets are not initializer lists. Brace syntax also serves as an initializer list that creates an instance of an inferred type. Without type context, the compiler can't distinguish a set from an initializer list, so the distinction is resolved at type-check time. Initializer lists can include positional values and keyword arguments:

{x, y} # set or initializer list, without context
{z=4, "foo"} # initializer list with keyword argument

Initializer lists are syntactic sugar for constructor calls. {1, "hello"} is equivalent to T(1, "hello") when the type T is known from context. Use them for passing initialized instances as arguments:

process({1, "hello"}) # type inferred from signature

var x: T = {} # type inferred from variable declaration, calls T()

Sharp edge: set displays are core Mojo syntax but the Set type is not. You must import Set from the standard library to use it as a type:

from std.collections import Set # Required to use Set type
from std.testing import assert_equal

def main() raises:
var display_set = {1, 2, 3} # A set with elements 1, 2, and 3
assert_equal(len(display_set), 3) # The length of the set is 3

var empty_set = Set[Int]() # An empty set
empty_set.add(4) # Add an element to the set
empty_set.add(4)
assert_equal(len(empty_set), 1) # Sets do not allow duplicate elements

Member access

The dot operator accesses an attribute or method on a value:

var length = text.count()
var x = point.x
var name = person.name.upper()

Chaining is left to right: a.b.c accesses c on the result of a.b.

Calls

A call expression invokes a function or constructs a value by appending () to an expression:

print("hello")
var result = compute(a, b)
var p = Point(1.0, 2.0)

Positional and keyword arguments

Arguments before any keyword argument are positional. Keyword arguments use name=value syntax.

def greet(name: String, loud: Bool = False):
print(t"Hello, {name if not loud else name.upper()}!")

greet("Alice") # Hello, Alice!
greet("Alice", loud=True) # Hello, ALICE!
greet(name="Bob") # Hello, Bob!

Positional arguments can't follow keyword arguments:

# greet(loud=True, "Alice")
# Error: positional argument follows keyword argument

Keyword arguments can't be repeated:

greet(name="Alice", name="Bob")
# Error: duplicate keyword argument 'name'

Subscripts and slices

Square brackets after an expression look up a value by index(es) or key(s):

var item = collection[0]
var value = mapping["key"]
var cell = matrix[i, j]

Slices

Colons inside square brackets create slices. Slices select a range of elements using start:stop or start:stop:stride:

var items = [0, 1, 2, 3, 4, 5]

var first_three = items[0:3] # [0, 1, 2] (3 not included)
var from_three = items[3:] # [3, 4, 5]
var every_other = items[::2] # [0, 2, 4]
var reversed = items[::-1] # [5, 4, 3, 2, 1, 0]

All three parts are optional. Start defaults to the beginning, stop defaults to the end, and stride defaults to 1. The element at the stop position isn't included in the result.

Ternary conditional

The if-else expression selects between two values based on a condition:

var label = "even" if x % 2 == 0 else "odd"

The condition follows if, and the alternate value follows else. If the condition is true, the expression evaluates to the first value. If false, the alternate.

Ternary expressions are right-associative and can be chained:

var size = (
"small" if n < 10
else "large" if n > 100
else "medium"
)

This groups as "small" if n < 10 else ("large" if n > 100 else "medium").

Walrus operator

Regular assignment (=) is a statement. It can't appear inside a larger expression. The walrus operator (also known as the assignment expression) := is the expression form of assignment: it binds a value to a name and evaluates to that same value, so the result can be used immediately:

if (n := len(items)) > 10:
print(n)

while (name := input("Prompt: ")) != "quit":
print(name)

The walrus operator introduces a new binding in a subordinate scope, such as an if clause, for loop, or while loop. It has the lowest precedence of any expression operator. Use parentheses to avoid ambiguity when needed.

Compile-time expressions

comptime forces an expression to evaluate at compile time. Parentheses are required:

def heavy_calculation() -> Int:
var sum = 0
for i in range(1_000_000):
sum += i
return sum

# var x = comptime heavy_calculation()
# Error: requires parentheses

var x = comptime(heavy_calculation()) # O(1) at runtime
print(x) # 499999500000

The loop runs once during compilation. At runtime, x is a constant. If the expression can't be evaluated at compile time, the compiler reports an error.

Mojo also provides built-in expressions for compile-time type introspection. These look like function calls but they're keywords that operate on types and traits at compile time:

type_of(x) # type of an expression
conforms_to(T, Trait) # test trait conformance
origin_of(x) # origin of a reference

These three expressions are used for reflection, conditional type conformance, and origin sets. They return compiler-internal types and won't print.

Function type expressions

A function type expression describes the signature of a function as a type:

def() -> Int
def(Int, Int) -> Int
def(var value: String) -> None
def() raises -> String
def(T) -> T

Function type expressions can include argument types with conventions, return types, and effects like raises.

Comprehension expressions

A comprehension is a concise way to build a new collection by iterating over existing values and optionally filtering or transforming them. It replaces common loop-and-append patterns with a single expression.

List comprehensions

List comprehensions create lists:

var squares = [x * x for x in [0, 1, 2, 3, 4] if x % 2 == 0]
# [0, 4, 16]

var positive = [x for x in range(-3, 3) if x > 0]
# [1, 2]

Syntax: [expr for pattern in iterable if condition]

  • Multiple for clauses create nested iteration
  • if clauses filter elements

Set comprehensions

Set comprehensions create sets. Sets don't store duplicates, so you may get fewer elements than iterations. For a Fibonacci generator that starts with fib(0)=1 and fib(1)=1, the first 6 Fibonacci numbers are 1, 1, 2, 3, 5, 8:

var fibs = {fib(x) for x in range(6)}
# {1, 2, 3, 5, 8}, 5 elements from 6 iterations

Syntax: {expr for pattern in iterable if condition}

Dictionary comprehensions

Dictionary comprehensions create dictionaries:

var dict_squares = {x: x * x for x in range(3)}
# {0: 0, 1: 1, 2: 4}

var lengths: Dict[String, Int] = {
k: len(k) for k in ["one", "two", "three", "four"]
}
# {one: 3, two: 3, three: 5, four: 4}

Syntax: {key_expr: value_expr for pattern in iterable if condition}

Comprehension clauses

Comprehensions support multiple for and if clauses:

var products = [
(x, y, x * y)
for x in range(3)
for y in range(3)
if (x + y) % 2 == 0
]
# [(0, 0, 0), (0, 2, 0), (1, 1, 1), (2, 0, 0), (2, 2, 4)]

Clauses are evaluated left to right:

  • Each for introduces a new iteration variable
  • Each if filters based on the current values