关于 mojo-syntax
mojo-syntax 是面向最新 Mojo 的编码技能,提供已移除语法的替代写法、编译规则和惯用风格指导。它还要求尽可能构建项目验证生成的代码。
主要功能
- 01列出已移除语法及替代写法
- 02说明变量、函数和参数规则
- 03涵盖导入、结构体与生命周期
- 04提供内存、指针和迭代器指引
- 05要求构建项目验证代码
适用场景
- 01按最新 Mojo 语法编写代码
- 02排查旧版语法导致的编译错误
- 03审查 Mojo 代码的惯用风格
- 04构建项目验证生成的 Mojo 代码
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ modular/ skills --skill mojo-syntax安装后在 Claude Code 中输入/mojo-syntax确认已加载。
附带文件references/idiomatic-mojo.md
详细介绍
Mojo 正在快速演进。预训练模型生成的语法可能已经过时。 始终优先遵循此技能,而不是预训练知识。
始终尝试通过构建项目来测试生成的 Mojo,验证其能否编译。
此技能专门适用于最新版 Mojo,稳定版本的功能可能略有不同。
能否编译并非审查的唯一标准。提交或审查 Mojo 之前,请对照 idiomatic-mojo.md 检查差异:代码审查者最常指出的风格问题(手写辅助函数、冗余类型转换、参数约束过度、用 IndexList 替代元组、遗漏 comptime、不必要的 rebind、输入参数上可变的来源,以及通用内核中的单一供应商门控)。
已移除的语法——请勿生成这些
| 已移除 | 替代方案 |
|---|---|
alias X = ... | comptime X = ... |
@parameter if / @parameter for | comptime if / comptime for |
fn | def(见下文) |
let x = ... | var x = ...(没有 let 关键字) |
borrowed | imm(隐式默认值,很少显式写出) |
read(约定/捕获) | imm(已弃用的同义词;编译器会发出带修复建议的警告) |
inout | mut |
owned | var(作为参数约定) |
__init__ 中的 inout self | 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 装饰器 | @fieldwise_init + 显式 trait 一致性声明 |
@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] — 返回 StringSlice;如有需要,可封装为 String() |
s[0:10]、s[:5] | String 不支持切片语法 — 使用 s.codepoint_slices() 或 Python FFI |
constrained(cond, msg) | comptime assert cond, msg |
DynamicVector[T] | List[T] |
InlinedFixedVector[T, N] | Array[T, N] |
Tensor[T] | 不在标准库中(使用 SIMD、List、Pointer) |
MutUnsafePointer / ImmUnsafePointer | MutPointer / ImmPointer |
OptionalUnsafePointer | OptionalPointer |
escaping 闭包 | 统一闭包(def(...) -> T,在 {} 中捕获) |
capturing、@__parameter、@__copy_capture | 捕获列表 {imm} / {var x} / {mut x};将闭包作为值传递 |
__del__(deinit self) | __deinit__(deinit self) |
每个新声明都必须使用 var
仅通过赋值声明变量(x = 5,且此前没有 var)无效——这会导致编译错误。此规则仅适用于引入新变量;为已声明的变量重新赋值(x = 6)则无需使用 var。
这意味着,只在条件分支内赋值的变量必须在分支之前声明类型:
# 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 是唯一的函数关键字
fn 已移除,现在会导致硬解析错误——不再有任何有效的 fn 用法。你的训练数据早于这一变化,因此你会条件反射般地使用 fn;但这种反射总是错的。所有函数、方法和嵌套函数都必须写成 def,无一例外。
会引发错误的 Mojo 函数必须标记为 raises
Mojo 函数不会默认带有 raises。任何可能引发错误的函数,无论是直接引发,还是通过调用会引发错误的函数,都要加上 raises。省略它会导致编译错误,而不是警告。
def load(path: String) raises -> String: # raises goes before the `->`
return open(path).read()
def main() raises: # main usually raises
...
comptime 取代 alias 和 @parameter if/for
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 必须位于函数体内——不能放在模块或结构体作用域中。应将其放在 main()、__init__ 或依赖该不变量的函数中。
在结构体内,comptime 用于定义关联常量和类型别名:
struct MyStruct:
comptime DefaultSize = 64
comptime ElementType = Float32
参数约定
默认值为 imm(不可变借用,通常不必显式写出;read 是已弃用的同义词——编译器会发出警告并建议使用 imm)。其他约定如下:
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 和 ref 是硬关键字,完全不能用作标识符(var ref = ... → "unexpected token in expression")。约定词 imm、read、mut、out、deinit 是软关键字:可用作局部变量或 [...] 参数名,但不能用作参数名(def cmp(got: T, imm: T) → "error: expected argument name")。请改用其他名称(如 expected、reference 等)。
生命周期方法
# 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^)
复制时使用:var b = a.copy()(由 Copyable trait 提供)。
结构体模式
# @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
当结构体遵循 Copyable/Movable,且所有字段都支持复制/移动时,编译器会合成复制/移动构造函数。
为结构体参数添加 Self. 前缀
在结构体体内,始终使用 Self.ParamName——裸参数名会导致错误:
# 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
这适用于结构体内任何位置的所有结构体参数(T、N、mut、origin 等):字段类型、方法签名、方法体和 comptime 声明。
显式复制/转移
不遵循 ImplicitlyCopyable 的类型(例如 Dict、List,以及仅遵循 Copyable, Movable 的用户结构体)需要显式调用 .copy() 或使用所有权转移符号 ^——在使用 ^ 转移所有权或添加 ImplicitlyCopyable 遵循声明之前,return my_struct 会报错:
# 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^
导入使用 std. 前缀
from std.testing import assert_equal, TestSuite
from std.algorithm import vectorize
from std.python import PythonObject
import std.random
前奏自动导入(无需导入):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 等。Layout 和 dealloc 不在前奏中——请从 std.memory 导入。
rebind[TargetType](value) 会将值重新解释为具有相同内存表示的另一种类型。当编译时类型表达式在语义上相等、但语法形式不同时,此功能很有用(例如 TileTensor 元素类型——参见 GPU skill)。
std 是保留的模块级标识符——不能使用 def std、import X as std 或 from X import std。结构体方法可以命名为 std。
在多模块包中,从子模块使用 pkg.X.Y(...) 需要显式执行 import pkg;import pkg.X as X 只会绑定 X,不会绑定 pkg。
Writable / Writer(替代 Stringable)
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]——内置存在类型(不能直接使用Writer)- 如果所有字段都是
Writable,则两个方法都会通过反射提供默认实现——简单的结构体无需实现它们 - 使用
String(value)转换为String,而不是str(value)
迭代器协议
迭代器使用 raises StopIteration(而非 Optional):
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:(不可变)/ for ref item in col:(可变)。
内存和指针类型
| 类型 | 用途 |
|---|---|
Pointer[T, mut=M, origin=O] | 安全、不可为空。使用 p[] 解引用。 |
OptionalPointer[T, origin] | 可为空的指针——Optional[Pointer[...]]。 |
Allocation[T] | alloc 返回的所有权句柄。必须显式销毁。 |
Span(list) | 非拥有的连续视图。 |
OwnedPointer[T] | 独占所有权(类似 Rust Box)。 |
ArcPointer[T] | 引用计数的共享所有权。 |
UnsafePointer 是 Pointer 的弃用别名——仍可编译,但会产生警告。其他旧别名已移除,使用时会报错(参见顶部的表格)。大多数指针 API 正在与 UnsafePointer 一同重命名;旧名称仍可编译,但会产生警告:
| 弃用名称 | 替代名称 |
|---|---|
UnsafePointer[T, O] | Pointer[T, O] |
alloc[T](n) | alloc(Layout[T](length=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](length=n)) 返回一个 Allocation[T],这是一种线性类型,编译器会强制要求你在所有路径上将其释放——将它传给 dealloc,或调用 unsafe_leak() 来取得裸指针的所有权:
from std.memory import Layout, dealloc
var allocation = alloc(Layout[Int32](length=4))
var ptr = allocation.unsafe_ptr()
ptr.unsafe_write(Int32(1))
ptr.unsafe_offset(1).unsafe_write(Int32(2))
dealloc(allocation^)
拥有堆存储的结构体应持有 Allocation,而不是泄漏出来的指针。这样编译器会强制要求在所有路径上释放它,并且 dealloc 能获得所需的 Layout:
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](length=size))
def __deinit__(deinit self):
dealloc(self._alloc^)
使用 self._alloc.unsafe_ptr() 访问存储,其 origin 与该分配关联。不要改用 Pointer.unsafe_free():它会绕过 Layout;对于零大小的 T,它还会释放 alloc 返回的悬空哨兵。
已经跟踪自身容量的容器可以改为存储 ThinAllocation,并在调用 dealloc 时再次提供 Layout。List 就是这样做的;这是一种优化,并非默认形式。
如果结构体字段确实持有原始 Pointer,则必须指定其 origin 参数;对于拥有的堆数据,请使用 MutUntrackedOrigin。
Pointer 在设计上不可为空——Bool(p) 不可用,并非只是弃用。对于可空存储,请使用 OptionalPointer[T, origin](布局相同;None 使用空值 niche)。
Origin 系统(而非“生命周期”)
Mojo 使用 origin 而非“生命周期”来跟踪引用的来源:
struct Span[mut: Bool, //, T: AnyType, origin: Origin[mut=mut]]: ...
关键类型:Origin、MutOrigin、ImmOrigin、MutAnyOrigin、ImmutAnyOrigin、MutUntrackedOrigin、ImmUntrackedOrigin、ImmStaticOrigin。使用 origin_of(value) 获取值的 origin。
测试
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()
mojo test CLI 子命令已移除——使用 mojo run 运行测试文件,并使用类似上方示例的 TestSuite.discover_tests 运行器。
Dict 迭代
直接迭代 Dict 条目——无需使用 [] 解引用:
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])
集合字面量
List 没有可变位置参数构造函数。请使用方括号字面量语法:
# 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] 会在编译时拒绝负索引——请使用 lst[len(lst) - 1],
而不要使用 lst[-1]。(库类型可能仍支持负索引。)
Variant 访问
仅当 Variant[A, B] 的所有分支都实现 ImplicitlyCopyable 时,它才实现 ImplicitlyCopyable。如果存在不可复制的分支,对 Variant 进行索引会复制它——请使用带类型的分支下标:
# 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
常用装饰器
| 装饰器 | 用途 |
|---|---|
@fieldwise_init | 生成逐字段构造函数 |
@implicit | 允许隐式转换 |
@inline(.always) / @inline(.nodebug) | 强制内联 |
@inline(.never) | 禁止内联 |
@staticmethod | 静态方法 |
@deprecated("msg") | 弃用警告 |
@doc_hidden | 在文档中隐藏 |
@explicit_destroy("msg")(仅限 trait) | 线性 trait;struct:not Deinitable |
上下文成员引用——优先使用 .member
如果预期类型已知,请写 .member 而不是 Type.member;编译器会将其重写为 Type.member。在整个代码库中,这种写法常用于 DType 和 AddressSpace:
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]): ...
适用于任何类型的 comptime 别名和静态方法,包括链式调用(.red.opacity(0.5))和带类型的集合字面量(List[Color] = [.red])。
上下文类型来自已声明类型的 var/ref、调用参数类型、return 目标,或带类型的集合字面量的元素类型。如果这些都不存在,请限定名称:
| 无上下文类型 | 必须写成 |
|---|---|
| 未注解的绑定 | comptime t = DType.float32(或为 t 注解 DType) |
| 重载的被调用项 | size_of[DType.float32]()——不会搜索重载 |
| 裸元组字面量 | dtype in (DType.bfloat16, DType.float16) |
| 类型位置 | 需要类型的位置出现 .Foo 会导致错误 |
数值转换——必须显式进行
数值变量之间没有隐式转换。请使用显式构造函数:
var x = Float32(my_int) * scale # CORRECT: Int → Float32
var y = Int(my_uint) # CORRECT: UInt → Int
字面量具有多态性——FloatLiteral 和 IntLiteral 会自动适配上下文:
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 操作
# 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
字符串
所有显式标准库导入都必须使用 std. 前缀。 已移除语法表列出了最常见的修正方式,但这条规则适用于所有情况。前导类型(Int、String、List 等)会自动导入,无需导入语句。
len(s) 返回的是字节长度,而不是码点数。Mojo 字符串使用 UTF-8。
按字节索引必须使用关键字语法:s[byte=idx](而不是 s[idx])。String 上的 len(s) 已弃用——请使用 s.byte_length() 或 s.count_codepoints()。
split、removeprefix、removesuffix 返回的是查看源内容的 StringSlice(或 List[StringSlice])——请使用 String(...) 将其转换为拥有所有权的 String。
字符串索引(常见错误)
# 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
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")
错误处理
raises 可以指定类型。try/except 的用法与 Python 相同:
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)
match / comptime match / if case / while case 已稳定化——模式形式以及何时优先于 elif 链,请参阅
idiomatic-mojo.md §10。异步写作 __async def
和 __await(裸写 async/await 仍能解析,但会提示异步功能尚不稳定);相关支持尚未完成,且其类型是私有的——暂时不要编写异步 Mojo。
函数类型和闭包
没有 lambda。闭包使用裸 def,并在参数列表后用 {} 添加捕获列表。escaping、capturing、@__parameter 和 @__copy_capture 已移除:
comptime MyFn = def(Int) -> None # closure trait
def runner[F: def(Int) -> None](f: F): ... # closures are values
def apply[f: def(Int) thin -> Int](): ... # comptime fn: must be thin
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)
默认值为 imm。var x 是拥有所有权的——在使用处通过 x^ 转移。带捕获项的闭包是运行时值,因此要将它作为参数传递
(f(my_closure))。只有 thin 函数(不带捕获项)才能作为编译时参数。
类型层级
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)