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.
Installs into .claude/skills of the current project.
Are you the author of Mojo Syntax?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/modular-mojo-syntax)
---
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` | `imm` (implicit default — rarely written) |
| `read` (convention / capture) | `imm` (deprecated synonym; the compiler warns with a fixit) |
| `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 move: 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]` | `Array[T, N]` |
| `Tensor[T]` | Not in stdlib (use SIMD, List, Pointer) |
| `MutUnsafePointer` / `ImmUnsafePointer` | `MutPointer` / `ImmPointer` |
| `OptionalUnsafePointer` | `OptionalPointer` |
| `escaping` closures | Unified closures (`def(...) -> T`, captures in `{}`); `capturing[_]` still valid |
| `__del__(deinit self)` | `__deinit__(deinit self)` |
## `var` is required for every new declaration
Declaring a variable with bare assignment (`x = 5` with no prior `var`) is
**not valid** — it is a compile error. This applies only to introducing a new
variable; reassigning an already-declared variable (`x = 6`) needs no `var`.
This means a variable assigned only inside conditional branches must be
predeclared with a type before the branch:
```mojo
# WRONG — no prior `var`, so this doesn't declare `x`
if cond:
x = 1
else:
x = 2
# CORRECT — declare with a type first, then assign in each branch
var x: Int
if cond:
x = 1
else:
x = 2
```
## `def` is the only function keyword
`fn` was **removed** and is now a hard parse error — no valid use of `fn`
remains. Your training predates this, so you will reach for `fn` by reflex; that
reflex is always wrong. Write **every** function, method, and nested function as
`def`, without exception.
## Mojo functions that raise must be marked as such
Mojo functions do **not** imply `raises`. Add `raises` to any function that can
raise, directly or by calling a raising function. Omitting it is a **compile
error**, not a warning.
```mojo
def load(path: String) raises -> String: # raises goes before the `->`
return open(path).read()
def main() raises: # main usually raises
...
```
## `comptime` replaces `alias` and `@parameter if`/`for`
```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 `imm` (immutable borrow, rarely written explicitly; `read` is a
deprecated synonym — the compiler warns and suggests `imm`). 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
```
`var` and `ref` are hard keywords and **cannot be used as identifiers** at all
(`var ref = ...` → `"unexpected token in expression"`). The convention words
`imm`, `read`, `mut`, `out`, `deinit` are soft keywords: fine as local variable
or `[...]` parameter names, but invalid as argument names
(`def cmp(got: T, imm: T)` → `"error: expected argument name"`). 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 move` arg)
def __init__(out self, *, deinit move: Self):
self.data = move.data^
# Destructor
def __deinit__(deinit self):
dealloc(self.allocation^)
```
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`,
`OptionalPointer`, `alloc`, `Span`, `Error`, `DType`, `Writable`, `Writer`,
`Copyable`, `Movable`, `Equatable`, `Hashable`, `rebind`, `print`, `range`,
`len`, and more. `Layout` and `dealloc` are **not** in the prelude — import them
from `std.memory`.
`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(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[]`. |
| `OptionalPointer[T, origin]` | Nullable pointer — `Optional[Pointer[...]]`. |
| `Allocation[T]` | Owning handle returned by `alloc`. Explicitly destroyed. |
| `Span(list)` | Non-owning contiguous view. |
| `OwnedPointer[T]` | Unique ownership (like Rust `Box`). |
| `ArcPointer[T]` | Reference-counted shared ownership. |
`UnsafePointer` is a deprecated alias of `Pointer` — it still compiles and
warns. The other legacy aliases were **removed** and are hard errors (see the
table at the top). Most of the pointer API is being renamed alongside
`UnsafePointer`; those old spellings still compile but warn:
| Deprecated | Replacement |
|---------------------------|-----------------------------------------|
| `UnsafePointer[T, O]` | `Pointer[T, O]` |
| `alloc[T](n)` | `alloc(Layout[T](count=n))` |
| `p.free()` | `dealloc(allocation^)` |
| `p[i]` | `p[unsafe_offset=i]` |
| `p + i` | `p.unsafe_offset(i)` |
| `p += i` | `p = p.unsafe_offset(i)` |
| `p.load()` / `p.store(v)` | `p.unsafe_load()` / `p.unsafe_store(v)` |
| `p.init_pointee_move(v)` | `p.unsafe_write(v)` |
| `p.init_pointee_copy(v)` | `p.unsafe_write(copy=v)` |
`alloc(Layout[T](count=n))` returns an `Allocation[T]`, a linear type the
compiler forces you to dispose of on every path — pass it to `dealloc`, or call
`unsafe_leak()` to take ownership of the bare pointer:
```mojo
from std.memory import Layout, dealloc
var allocation = alloc(Layout[Int32](count=4))
var ptr = allocation.unsafe_ptr()
ptr.unsafe_write(Int32(1))
ptr.unsafe_offset(1).unsafe_write(Int32(2))
dealloc(allocation^)
```
A struct that owns heap storage should hold the `Allocation`, not a leaked
pointer. The compiler then enforces disposal on every path, and `dealloc`
gets the `Layout` it needs:
```mojo
from std.memory import Layout, Allocation, alloc, dealloc
struct Buffer[T: AnyType]:
var _alloc: Allocation[Self.T]
def __init__(out self, size: Int):
self._alloc = alloc(Layout[Self.T](count=size))
def __deinit__(deinit self):
dealloc(self._alloc^)
```
Get at the storage with `self._alloc.unsafe_ptr()`, whose origin is tied to the
allocation. Don't substitute `Pointer.unsafe_free()`: it bypasses the `Layout`,
and for zero-sized `T` it frees the dangling sentinel that `alloc` returns.
A container that already tracks its own capacity may instead store a
`ThinAllocation` and supply the `Layout` again at `dealloc` time. That is
what `List` does; it is an optimization, not the default shape.
When a struct field does hold a raw `Pointer`, its `origin` parameter must be
specified; use `MutUntrackedOrigin` for owned heap data.
`Pointer` is **non-null by design** — `Bool(p)` is unavailable, not merely
deprecated. For nullable storage, use `OptionalPointer[T, origin]` (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`, `ImmOrigin`, `MutAnyOrigin`,
`ImmutAnyOrigin`, `MutUntrackedOrigin`, `ImmUntrackedOrigin`,
`ImmStaticOrigin`. 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].unwrap[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 |
| `@inline(.always)` / `@inline(.nodebug)` | Force inline |
| `@inline(.never)` | Prevent inline |
| `@staticmethod` | Static method |
| `@deprecated("msg")` | Deprecation warning |
| `@doc_hidden` | Hide from docs |
| `@explicit_destroy` | Linear type (no implicit destruction) |
## Contextual member references — prefer `.member`
Where the expected type is already known, write `.member` instead of
`Type.member`; the compiler rewrites it to `Type.member`. Idiomatic
throughout the codebase for `DType` and `AddressSpace`:
```mojo
var v = SIMD[.float32, 4](1.0, 2.0, 3.0, 4.0) # not SIMD[DType.float32, 4]
comptime if dtype == .bfloat16: ... # `__eq__` arg supplies context
v.cast[.float32]()
ctx.enqueue_create_buffer[.int32](num_rows)
unsafe_stack_allocation[1, Int32, address_space=.SHARED]()
def f[dtype: DType = .float32](x: TileTensor[.bfloat16, L, MutAnyOrigin]): ...
```
Works for any type's `comptime` aliases and static methods, including chains
(`.red.opacity(0.5)`) and typed collection literals (`List[Color] = [.red]`).
The context comes from a declared `var`/`ref` type, a call-argument type, a
`return` destination, or a typed collection literal's element type. With none
of those, qualify the name:
| No contextual type | Must write |
|-------------------------------|--------------------------------------------------------|
| Unannotated binding | `comptime t = DType.float32` (or annotate `t: DType`) |
| Overloaded callee | `size_of[DType.float32]()` — overloads aren't searched |
| Bare tuple literal | `dtype in (DType.bfloat16, DType.float16)` |
| Type position | `.Foo` where a *type* is expected is an error |
## 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[.float32, 4](1.0, 2.0, 3.0, 4.0) # literals adapt
```
## SIMD operations
```mojo
# Construction and lane access
var v = SIMD[.float32, 4](1.0, 2.0, 3.0, 4.0)
v[0] # read lane → Scalar[.float32]
v[0] = 5.0 # write lane
# Type cast
v.cast[.uint32]() # element-wise → SIMD[.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.gt(0.0) # SIMD[.bool, 4] — `v > 0.0` is
# Scalar-only and fails to compile
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. `async def` and `await` parse, but async support is
unfinished and its types are private — do not write async Mojo yet.
## 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, imm ptr, var x}: # captures: mut/imm/var
count += ptr[i] + x^ # `^` at use site, not in `{}`
vectorize[simd_width](size, closure) # runtime-arg overload
```
`imm` is default. `var x` is owned — transfer with `x^` at the use site.
Prefer unified closures with a capture list. Do not use `@__parameter` /
`@parameter` on nested closures in new or migrated code — the legacy form is
slated for removal. Pass closures as runtime arguments (`f(my_closure)`)
where possible; if an API requires a comptime `capturing[_]` function, use
`def … capturing` without `@__parameter`, or migrate that API.
## Type hierarchy
```text
AnyType
Deinitable — auto __deinit__; most types
Movable — __init__(out self, *, deinit move: Self)
Copyable — __init__(out self, *, copy: Self)
ImplicitlyCopyable(Copyable, take)
RegisterPassable(Movable)
TrivialRegisterPassable(ImplicitlyCopyable, take, Movable, RegisterPassable)
```