mcpField
M
mojo-syntax

mojo-syntax

github.com/modular开发2072.9k 次安装3 天前更新
代码仓库

关于 mojo-syntax

mojo-syntax 是面向最新 Mojo 的编码技能,提供已移除语法的替代写法、编译规则和惯用风格指导。它还要求尽可能构建项目验证生成的代码。

主要功能

  1. 01列出已移除语法及替代写法
  2. 02说明变量、函数和参数规则
  3. 03涵盖导入、结构体与生命周期
  4. 04提供内存、指针和迭代器指引
  5. 05要求构建项目验证代码

适用场景

  1. 01按最新 Mojo 语法编写代码
  2. 02排查旧版语法导致的编译错误
  3. 03审查 Mojo 代码的惯用风格
  4. 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 forcomptime if / comptime for
fndef(见下文)
let x = ...var x = ...(没有 let 关键字)
borrowedimm(隐式默认值,很少显式写出)
read(约定/捕获)imm(已弃用的同义词;编译器会发出带修复建议的警告)
inoutmut
ownedvar(作为参数约定)
__init__ 中的 inout selfout 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_passableRegisterPassable 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 / ImmUnsafePointerMutPointer / ImmPointer
OptionalUnsafePointerOptionalPointer
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 + ip.unsafe_offset(i)
p += ip = 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)

评论

支持 Markdown