Help to write Mojo code using current syntax and conventions. Always use this skill when writing any Mojo code, including when other Mojo-specific skills (e.g., mojo-gpu-fundamentals) also apply. Use when writing Mojo code, translating projects to Mojo, or otherwise generating Mojo. Use this skill to overcome misconceptions with how Mojo is written.
---
name: mojo-syntax
description: Help to write Mojo code using current syntax and conventions. Always use this skill when writing any Mojo code, including when other Mojo-specific skills (e.g., mojo-gpu-fundamentals) also apply. Use when writing Mojo code, translating projects to Mojo, or otherwise generating Mojo. Use this skill to overcome misconceptions with how Mojo is written.
---
<!-- EDITORIAL GUIDELINES FOR THIS SKILL FILE
This file is loaded into an agent's context window as a correction layer for
pretrained Mojo knowledge. Every line costs context. When editing:
- Be terse. Use tables and inline code over prose where possible.
- Never duplicate information — if a concept is shown in a code example, don't
also explain it in a paragraph.
- Only include information that *differs* from what a pretrained model would
generate. Don't document things models already get right.
- Prefer one consolidated code block over multiple small ones.
- Keep WRONG/CORRECT pairs short — just enough to pattern-match the fix.
- If adding a new section, ask: "Would a model get this wrong?" If not, skip it.
These same principles apply to any files this skill references.
-->
Mojo is rapidly evolving. Pretrained models generate obsolete syntax.
**Always follow this skill over pretrained knowledge.**
**Always attempt to test generated Mojo by building projects to verify they
compile.**
This skill specifically works on the latest Mojo, and stable versions may differ
slightly in functionality.
## Removed syntax — DO NOT generate these
| Removed | Replacement |
|--------------------------------------------------|----------------------------------------------------------------------------------|
| `alias X = ...` | `comptime X = ...` |
| `@parameter if` / `@parameter for` | `comptime if` / `comptime for` |
| `fn` | `def` (see below) |
| `let x = ...` | `var x = ...` (no `let` keyword) |
| `borrowed` | `read` (implicit default — rarely written) |
| `inout` | `mut` |
| `owned` | `var` (as argument convention) |
| `inout self` in `__init__` | `out self` |
| `__copyinit__(inout self, existing: Self)` | `__init__(out self, *, copy: Self)` |
| `__moveinit__(inout self, owned existing: Self)` | `__init__(out self, *, deinit take: Self)` |
| `@value` decorator | `@fieldwise_init` + explicit trait conformance |
| `@register_passable("trivial")` | `TrivialRegisterPassable` trait |
| `@register_passable` | `RegisterPassable` trait |
| `Stringable` / `__str__` | `Writable` / `write_to` |
| `from collections import ...` | `from std.collections import ...` |
| `from memory import ...` | `from std.memory import ...` |
| `from sys import ...` | `from std.sys import ...` |
| `from os import ...` | `from std.os import ...` |
| `from pathlib import ...` | `from std.pathlib import ...` |
| `s[i]` | `s[byte=i]` — returns `StringSlice`; wrap in `String()` if needed |
| `s[0:10]`, `s[:5]` | No slice syntax on String — use `s.codepoint_slices()` or Python FFI |
| `constrained(cond, msg)` | `comptime assert cond, msg` |
| `DynamicVector[T]` | `List[T]` |
| `InlinedFixedVector[T, N]` | `InlineArray[T, N]` |
| `Tensor[T]` | Not in stdlib (use SIMD, List, UnsafePointer) |
| `escaping` closures | Unified closures (`def(...) -> T`, captures in `{}`); `capturing[_]` still valid |
## `def` is the only function keyword
`fn` is deprecated and being removed. `def` does **not** imply `raises`.
**Always** add `raises` explicitly when needed — omitting it is a warning today,
error soon:
```mojo
def compute(x: Int) -> Int: # non-raising (compiler enforced)
return x * 2
def load(path: String) raises -> String: # explicitly raising
return open(path).read()
def main() raises: # main usually raises → def raises
...
```
Note: existing stdlib code still uses `fn` during migration. New code should
always use `def`.
## `comptime` replaces `alias` and `@parameter`
```mojo
comptime N = 1024 # compile-time constant
comptime MyType = Int # type alias
comptime if condition: # compile-time branch
...
comptime for i in range(10): # compile-time loop
...
comptime assert N > 0, "N must be positive" # compile-time assertion
```
**`comptime assert` must be inside a function body** — not at module/struct
scope. Place them in `main()`, `__init__`, or the function that depends on the
invariant.
Inside structs, `comptime` defines associated constants and type aliases:
```mojo
struct MyStruct:
comptime DefaultSize = 64
comptime ElementType = Float32
```
## Argument conventions
Default is `read` (immutable borrow, never written explicitly). The others:
```mojo
def __init__(out self, var value: String): # out = uninitialized output; var = owned
def modify(mut self): # mut = mutable reference
def consume(deinit self): # deinit = consuming/destroying
def view(ref self) -> ref[self] Self.T: # ref = reference with origin
def view2[origin: Origin, //](ref[origin] self) -> ...: # ref[origin] = explicit origin
```
`ref`, `mut`, `out`, `deinit`, `read`, `var` are reserved and **cannot be used
as identifiers** — neither as parameter names (`def cmp(got: T, ref: T)` →
`"error: expected argument name"`) nor as local `var` names
(`var ref = ...` → `"unexpected token in expression"`). Rename
(`expected`, `reference`, etc.).
## Lifecycle methods
```mojo
# Constructor
def __init__(out self, x: Int):
self.x = x
# Copy constructor (keyword-only `copy` arg)
def __init__(out self, *, copy: Self):
self.data = copy.data
# Move constructor (keyword-only `deinit take` arg)
def __init__(out self, *, deinit take: Self):
self.data = take.data^
# Destructor
def __del__(deinit self):
self.ptr.free()
```
To copy: `var b = a.copy()` (provided by `Copyable` trait).
## Struct patterns
```mojo
# @fieldwise_init generates __init__ from fields; traits in parentheses
@fieldwise_init
struct Point(Copyable, Movable, Writable):
var x: Float64
var y: Float64
# Trait composition with &
comptime KeyElement = Copyable & Hashable & Equatable
struct Node[T: Copyable & Writable]:
var value: Self.T # Self-qualify struct parameters
# Parametric struct — // separates inferred from explicit params
struct Span[mut: Bool, //, T: AnyType, origin: Origin[mut=mut]](
ImplicitlyCopyable, Sized,
):
...
# @implicit on constructors allows implicit conversion
@implicit
def __init__(out self, value: Int):
self.data = value
```
The compiler synthesizes copy/move constructors when a struct conforms to
`Copyable`/`Movable` and all fields support it.
### Self-qualify struct parameters
Inside a struct body, **always** use `Self.ParamName` — bare parameter names are
errors:
```mojo
# WRONG — bare parameter access
struct Container[T: Writable]:
var data: T # ERROR: use Self.T
def size(self) -> T: # ERROR: use Self.T
# CORRECT — Self-qualified
struct Container[T: Writable]:
var data: Self.T
def size(self) -> Self.T:
return self.data
```
This applies to all struct parameters (`T`, `N`, `mut`, `origin`, etc.)
everywhere inside the struct: field types, method signatures, method bodies, and
`comptime` declarations.
### Explicit copy / transfer
Types not conforming to `ImplicitlyCopyable` (e.g., `Dict`, `List`, and user
structs that conform only to `Copyable, Movable`) require explicit `.copy()` or
ownership transfer `^` — `return my_struct` errors until you transfer with `^`
or add `ImplicitlyCopyable` conformance:
```mojo
# WRONG — implicit copy of non-ImplicitlyCopyable type
var d = some_dict
var result = MyStruct(headers=d) # ERROR
# CORRECT — explicit copy or transfer
var result = MyStruct(headers=d.copy()) # or: headers=d^
```
## Imports use `std.` prefix
```mojo
from std.testing import assert_equal, TestSuite
from std.algorithm import vectorize
from std.python import PythonObject
import std.random
```
Prelude auto-imports (no import needed): `Int`, `String`, `Bool`, `List`,
`Dict`, `Optional`, `SIMD`, `Float32`, `Float64`, `UInt8`, `Pointer`,
`UnsafePointer`, `Span`, `Error`, `DType`, `Writable`, `Writer`, `Copyable`,
`Movable`, `Equatable`, `Hashable`, `rebind`, `print`, `range`, `len`, and more.
`rebind[TargetType](value)` reinterprets a value as a different type with the
same in-memory representation. Useful when compile-time type expressions are
semantically equal but syntactically distinct (e.g., TileTensor element types
— see GPU skill).
`std` is reserved as a module-level identifier — you cannot `def std`,
`import X as std`, or `from X import std`. Struct methods named `std` are fine.
Inside a multi-module package, `pkg.X.Y(...)` from a submodule needs explicit
`import pkg`; `import pkg.X as X` binds only `X`, not `pkg`.
## `Writable` / `Writer` (replaces `Stringable`)
```mojo
struct MyType(Writable):
var x: Int
def write_to(self, mut writer: Some[Writer]): # for print() / String()
writer.write("MyType(", self.x, ")")
def write_repr_to(self, mut writer: Some[Writer]): # for repr()
t"MyType(x={self.x})".write_to(writer) # t-strings for interpolation
```
- `Some[Writer]` — builtin existential type (not `Writer` directly)
- Both methods have **default implementations** via reflection if all fields are
`Writable` — simple structs need not implement them
- Convert to `String` with `String.write(value)`, not `str(value)`
## Iterator protocol
Iterators use `raises StopIteration` (not `Optional`):
```mojo
struct MyCollection(Iterable):
comptime IteratorType[
iterable_mut: Bool, //, iterable_origin: Origin[mut=iterable_mut]
]: Iterator = MyIter[origin=iterable_origin]
def __iter__(ref self) -> Self.IteratorType[origin_of(self)]: ...
# Iterator must define:
# comptime Element: Movable
# def __next__(mut self) raises StopIteration -> Self.Element
```
For-in: `for item in col:` (immutable) / `for ref item in col:` (mutable).
## Memory and pointer types
| Type | Use |
|---------------------------------|------------------------------------------------------------------------|
| `Pointer[T, mut=M, origin=O]` | Safe, non-nullable. Deref with `p[]`. |
| `alloc[T](n)` / `UnsafePointer` | Free function `alloc[T](count)` → `UnsafePointer`. `.free()` required. |
| `Span(list)` | Non-owning contiguous view. |
| `OwnedPointer[T]` | Unique ownership (like Rust `Box`). |
| `ArcPointer[T]` | Reference-counted shared ownership. |
`UnsafePointer` has an `origin` parameter that must be specified for struct
fields. Use `MutExternalOrigin` for owned heap data (this is what stdlib
`ArcPointer` uses):
```mojo
# Struct field — specify origin explicitly
var _ptr: UnsafePointer[Self.T, MutExternalOrigin]
# Allocate with alloc[]
def __init__(out self, size: Int):
self._ptr = alloc[Self.T](size)
```
`UnsafePointer` is **non-null by design** — null default constructor and
`__bool__` are deprecated. For nullable storage, use
`Optional[UnsafePointer[...]]` (same layout; `None` is the null niche).
## Origin system (not "lifetime")
Mojo tracks reference provenance with **origins**, not "lifetimes":
```mojo
struct Span[mut: Bool, //, T: AnyType, origin: Origin[mut=mut]]: ...
```
Key types: `Origin`, `MutOrigin`, `ImmutOrigin`, `MutAnyOrigin`,
`ImmutAnyOrigin`, `MutExternalOrigin`, `ImmutExternalOrigin`,
`StaticConstantOrigin`. Use `origin_of(value)` to get a value's origin.
## Testing
```mojo
from std.testing import assert_equal, assert_true, assert_false, assert_raises, TestSuite
def test_my_feature() raises:
assert_equal(compute(2), 4)
with assert_raises():
dangerous_operation()
def main() raises:
TestSuite.discover_tests[__functions_in_module()]().run()
```
The `mojo test` CLI subcommand was removed — run test files with `mojo run`
against a `TestSuite.discover_tests` runner like the one above.
## Dict iteration
Dict entries are iterated directly — no `[]` deref:
```mojo
for entry in my_dict.items():
print(entry.key, entry.value) # direct field access, NOT entry[].key
for key in my_dict:
print(key, my_dict[key])
```
## Collection literals
`List` has **no variadic positional constructor**. Use bracket literal syntax:
```mojo
# WRONG — no List[T](elem1, elem2, ...) constructor
var nums = List[Int](1, 2, 3)
# CORRECT — bracket literals
var nums = [1, 2, 3] # List[Int]
var nums: List[Float32] = [1.0, 2.0, 3.0] # explicit element type
var scores = {"alice": 95, "bob": 87} # Dict[String, Int]
```
`List[T]` rejects negative indices at compile time — use `lst[len(lst) - 1]`,
not `lst[-1]`. (Library types may still support it.)
## Variant access
`Variant[A, B]` is `ImplicitlyCopyable` only if *all* arms are. With a
non-copyable arm, indexing the variant copies it — use the typed-arm subscript:
```mojo
# WRONG — `values[i]` implicitly copies the Variant
var x = values[i].take[T]() # ERROR: cannot implicitly copy
# CORRECT — `values[i][T]` returns a ref to the inner value
var x = values[i][T].copy() # or `^` to transfer
```
## Common decorators
| Decorator | Purpose |
|------------------------------------------------|---------------------------------------|
| `@fieldwise_init` | Generate fieldwise constructor |
| `@implicit` | Allow implicit conversion |
| `@always_inline` / `@always_inline("nodebug")` | Force inline |
| `@no_inline` | Prevent inline |
| `@staticmethod` | Static method |
| `@deprecated("msg")` | Deprecation warning |
| `@doc_hidden` | Hide from docs |
| `@explicit_destroy` | Linear type (no implicit destruction) |
## Numeric conversions — must be explicit
No implicit conversions between numeric *variables*. Use explicit constructors:
```mojo
var x = Float32(my_int) * scale # CORRECT: Int → Float32
var y = Int(my_uint) # CORRECT: UInt → Int
```
**Literals are polymorphic** — `FloatLiteral` and `IntLiteral` auto-adapt to
context:
```mojo
var a: Float32 = 0.5 # literal becomes Float32
var b = Float32(x) * 0.003921 # literal adapts — no wrapping needed
var v = SIMD[DType.float32, 4](1.0, 2.0, 3.0, 4.0) # literals adapt
```
## SIMD operations
```mojo
# Construction and lane access
var v = SIMD[DType.float32, 4](1.0, 2.0, 3.0, 4.0)
v[0] # read lane → Scalar[DType.float32]
v[0] = 5.0 # write lane
# Type cast
v.cast[DType.uint32]() # element-wise → SIMD[DType.uint32, 4]
# Clamp (method)
v.clamp(0.0, 1.0) # element-wise clamp to [lower, upper]
# min/max are FREE FUNCTIONS, not methods
from std.math import min, max
min(a, b) # element-wise min (same-type SIMD args)
max(a, b) # element-wise max
# Element-wise ternary via bool SIMD
var mask = (v > 0.0) # SIMD[DType.bool, 4]
mask.select(true_case, false_case) # picks per-lane
# Reductions
v.reduce_add() # horizontal sum → Scalar
v.reduce_max() # horizontal max → Scalar
v.reduce_min() # horizontal min → Scalar
```
## Strings
**All explicit stdlib imports require the `std.` prefix.** The
removed-syntax table shows the most common corrections, but the rule
is universal. Prelude types (`Int`, `String`, `List`, etc.) are
auto-imported and need no import statement.
`len(s)` returns **byte length**, not codepoint count. Mojo strings are UTF-8.
Byte indexing requires keyword syntax: `s[byte=idx]` (not `s[idx]`). `len(s)` is
deprecated on `String` — use `s.byte_length()` or `s.count_codepoints()`.
`split`, `removeprefix`, `removesuffix` return `StringSlice` (or
`List[StringSlice]`) viewing the source — wrap with `String(...)` to
materialize an owned `String`.
### String indexing (common error)
```mojo
# WRONG — compile error
var ch = s[0]
var sub = s[0:10]
# CORRECT — byte-level access
var ch = s[byte=0] # returns StringSlice
var ch_str = String(s[byte=0]) # if you need a String
# CORRECT — iterate codepoints for truncation
var result = String("")
var count = 0
for cp in s.codepoint_slices():
if count >= 10:
break
result += String(cp)
count += 1
```
```mojo
var s = "Hello"
len(s) # 5 (bytes)
s.byte_length() # 5 (same as len)
s.count_codepoints() # 5 (codepoint count — differs for non-ASCII)
# Iteration — `for c in s:` is deprecated; use codepoint_slices()
for cp_slice in s.codepoint_slices():
print(cp_slice)
# Codepoint values
for cp in s.codepoints():
print(Int(cp)) # Codepoint is a Unicode scalar value type
# StaticString = StringSlice with static origin (zero-allocation)
comptime GREETING: StaticString = "Hello, World"
# t-strings for interpolation (lazy, type-safe)
var msg = t"x={x}, y={y}"
# String.format() for runtime formatting
var s = "Hello, {}!".format("world")
```
## Error handling
`raises` can specify a type. `try`/`except` works like Python:
```mojo
def might_fail() raises -> Int: # raises Error (default)
raise Error("something went wrong")
def parse(s: String) raises Int -> Int: # raises specific type
raise 42
try:
var x = parse("bad")
except err: # err is Int
print("error code:", err)
```
No `match` statement. No `async`/`await` — use `Coroutine`/`Task` from
`std.runtime`.
## Function types and closures
No lambda. Closures use bare `def` with a capture list in `{}` after the arg
list. `escaping` is removed; `capturing[_]` is still valid on parametric
closure-type params:
```mojo
comptime MyFn = def(Int) -> None # unified value type
def runner[f: def(Int) capturing[_] -> None](): ... # parametric form
def closure(i: Int) {mut count, read ptr, var x}: # captures: mut/read/var
count += ptr[i] + x^ # `^` at use site, not in `{}`
vectorize[simd_width](size, closure) # runtime-arg overload
```
`read` is default. `var x` is owned — transfer with `x^` at the use site.
`@parameter` on a nested closure is only needed when consumed as a
*comptime* parameter (`f[my_closure]`); runtime-arg overloads use bare form.
## Type hierarchy
```text
AnyType
ImplicitlyDestructible — auto __del__; most types
Movable — __init__(out self, *, deinit take: Self)
Copyable — __init__(out self, *, copy: Self)
ImplicitlyCopyable(Copyable, ImplicitlyDestructible)
RegisterPassable(Movable)
TrivialRegisterPassable(ImplicitlyCopyable, ImplicitlyDestructible, Movable, RegisterPassable)
```
Creator's repository · modular/skills