--- 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. --- 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) ```