要点
Mojo 的基础条目按用途分成四组:声明与结构、函数与参数、常用类型与集合、内存与互操作。每个条目给签名、一句话语义、最小示例和坑,示例自包含,粘进 25.4 时点的 pixi run mojo repl 或文件里就能跑。字典是查的不是读的——”为什么这样设计”的叙事版在《Mojo 语法要点》,本文只做条目式速查,文末速查表是全量索引。Mojo 处于快速演进期,版本敏感的写法在条目里单独标注,示例以 25.4(2025-06-18 发布)为准。
声明与结构
组内共性:Mojo 有两个世界——def 代表的动态世界、fn/struct/alias 代表的编译期世界。这组条目决定你的代码活在哪个世界里,混用时的大部分编译报错都源于世界的错配。
def 与 fn
def parse(raw): # 动态模式:参数可不标类型
parts = raw.split(",") # 变量可隐式声明
return parts
fn circle_area(r: Float64) -> Float64: # 严格模式:类型与返回值必标
return 3.14159 * r * r
脚本与胶水代码用 def,性能关键路径用 fn;同一文件可以混用,fn 可以被 def 调用。
⚠️ 坑:同一个”改参数”的动作在两边行为不同——def 的参数是可变副本(改了不影响调用者),fn 的参数默认只读借用(赋值直接编译报错)。从 Python 拷来的函数在 fn 里报错,十有八九是这个。
var
fn f() -> Int:
var x = 10 # fn 里必须 var 显式声明
x += 1
return x
var 声明可变局部变量,是 fn 世界里变量出生的唯一方式。
⚠️ 坑:24.x 之前旧代码里的 let 关键字已经删除,见到 let 一律当 var 读;def 里不写 var 也能隐式声明,fn 里不行——”undeclared variable” 报错先补 var。
struct 与 @fieldwise_init
@fieldwise_init # 自动生成逐字段构造器
struct Point:
var x: Float64 # 字段类型必标
var y: Float64
fn main():
var p = Point(x=1.0, y=2.0)
print(p.x + p.y) # 3.0
静态类型载体:字段与方法编译期确定,内存布局与 C 结构体同量级。
⚠️ 坑:没有继承,”找个父类”这条路不存在(复用走 trait);旧教程的 @value 已弃用,对应关系是换算成 @fieldwise_init;不写 @fieldwise_init 也不手写 __init__ 时,Point(...) 这种构造调用不存在。
trait 遵循
struct Pair(Copyable, Movable): # 遵循声明写在结构体名后的括号里
var k: Int
var v: Float64
trait 是 Mojo 的”接口”:约束泛型参数、给类型贴能力标签,常见内置 trait 有 AnyType、Copyable、Movable、Stringable、Intable、Equatable、Hashable 等。
⚠️ 坑:需要拷贝/移动某 struct 而编译器报 trait 错误时,多数是括号里漏声明或字段不可拷贝;实现 trait 的方法名在 25.x 间有过改名(部分 dunder 改为普通名),具体拼写以官方文档为准。
alias
alias N = 128 # 编译期常量
alias WIDTH = 4
var v = SIMD[DType.float64, WIDTH](0) # 能进方括号参数
编译期求值的命名常量,是 struct/函数方括号参数世界的”货币”。
⚠️ 坑:与 var 的区别不是”不可变”,是”存在于哪个世界”——var 的值进不了编译期参数位;alias 只能装编译期可求值的表达式,塞运行时才有的东西直接报错。官方仓库里 alias 改名的讨论已挂多年,语义稳定、拼写可能变,以官方文档为准。
函数与参数
组内共性:Mojo 的函数签名即文档——参数怎么进来、会不会改、错误怎么走,全部写在签名里,读签名就能预判行为。
参数约定 read / mut / owned / out
from collections import List
fn fill(mut buf: List[Float64], v: Float64): # mut:改动写回调用者
for i in range(len(buf)):
buf[i] = v
fn main():
var xs = List[Float64](0.0, 0.0)
fill(xs, 9.9)
print(xs) # [9.9, 9.9]
默认(read)只读借用零拷贝;mut 可变借用;owned 接管所有权;out 用于构造器与结果参数(__init__(out self))。
⚠️ 坑:fn 参数赋值报错不是语言 bug,是默认 read 在工作——想改就标 mut,想消耗才用 owned,别为了消报错乱标;读 24.x 老教程要换算:borrowed 即现在的默认 read,inout 即 mut。
方括号编译期参数
fn zeros[W: Int]() -> SIMD[DType.float64, W]:
return SIMD[DType.float64, W](0)
fn main():
var v = zeros[4]() # 方括号实参编译期定型
print(v.reduce_add()) # 0.0
方括号是编译期参数、圆括号是运行时参数:一个函数两套维度,宽度、形状、类型这类”结构性信息”走方括号。
⚠️ 坑:方括号实参必须是编译期已知(字面量或 alias),报错出现 “must be known at compile time” 时,把运行时算出来的值改成 alias 才是正解,绕不是办法。
@parameter if
alias WIDTH = 8
fn pick() -> Int:
@parameter if WIDTH >= 8:
return WIDTH # 编译期分支:不成立的分支整体消失
else:
return 1
编译期条件分支,配合 alias 实现同一份代码的多条编译路径。
⚠️ 坑:条件必须是编译期值,拿运行时变量当条件直接报错——那是普通 if 的活。
raise 与 try / except
fn checked_div(a: Float64, b: Float64) raises -> Float64:
if b == 0.0:
raise Error("division by zero")
return a / b
def main():
try:
checked_div(1.0, 0.0)
except e:
print("failed:", e)
错误处理是 Python 风格的 raise/try/except,错误对象以 Error 为基类。
⚠️ 坑:fn 会抛错就必须在签名里写 raises,漏写是高频编译错误;def 可以隐式抛错不用声明。自定义错误类型的继承写法与机制仍在演进,以官方文档为准。
常用类型与集合
组内共性:名字与 Python 同名的类型,语义都是”编译期固定类型”的版本——类型参数在方括号里,元素类型不可变着玩。
Int
fn main():
var a: Int = 42
var f = 7 / 2 # Int / Int 得 Float64
var g = 7 // 2 # 整除用 //
print(f, g) # 3.5 3
固定宽度的带符号整数(常见实现为 64 位),不是 Python 的任意精度 int。
⚠️ 坑:从 Python 迁移的算法里,依赖”大数自动升位”的代码要重写;除法语义与 Python 有差异——/ 得浮点、// 才是整除,混着写会产生难查的精度问题。
Float64 与 SIMD
fn main():
var x: Float64 = 3.14 # Float64 = SIMD[DType.float64, 1]
var v = SIMD[DType.float32, 8](0) # 8 通道全零
var w = v * 2.0 + 1.0 # 逐通道算术
print(w.reduce_max()) # 归约取最大通道
浮点标量是宽度 1 的 SIMD 别名,向量化就是把宽度参数从 1 调大,逐通道运算与归约(reduce_add / reduce_max / reduce_min)是高频操作。
⚠️ 坑:不同宽度、不同精度的 SIMD 混算需要显式转换(cast 一族方法),隐式混合直接报错;Int 不在 SIMD 体系里,”标量都是 SIMD”只对浮点成立。
String
fn main():
var n = str(25) # 内置 str:数字转字符串
print("Mojo", n, 25.4) # print 收多参数,空格分隔
UTF-8 字符串类型,日常拼装与打印够用。
⚠️ 坑:字符串类型体系在 25.x 持续重构(String 与 Span/StringSlice 一族的关系、方法集都有变动),切片与方法名以官方文档为准,别把 Python str 的全部方法集当成默认存在。
List
from collections import List
fn main():
var xs = List[Float64](1.0, 2.0, 3.0)
xs.append(4.0)
print(len(xs)) # 4
动态数组,元素类型编译期固定,append/len/下标读写覆盖日常。
⚠️ 坑:25.x 起 List/Dict 属于 collections 模块,忘了 from collections import List 是报错头名(更早版本是内置名);Python 心智的 xs + [5.0] 拼接写法不存在,用 append。
Dict
from collections import Dict
fn main():
var d = Dict[String, Int]()
d["a"] = 1
print(len(d)) # 1
哈希表,键值类型编译期固定。
⚠️ 坑:缺键的 [] 访问会抛错,Python dict.get 的兜底习惯不能直接搬(兜底写法以官方文档为准);键类型要满足可哈希约束(KeyElement 一族 trait),自定义 struct 当键时先确认遵循。
内存与互操作
组内共性:这组条目是 Mojo 区别于 Python 的”系统层”,收益是贴近硬件的性能,代价是安全检查交还给你自己。
UnsafePointer
fn main():
var p = UnsafePointer[Float64].alloc(4) # 堆上 4 个 Float64
p.store(0, 1.25) # 写单个元素
print(p.load(0)) # 读单个元素
裸指针:手动分配(alloc)、按偏移读写(load/store)、向量化批量读写走 load/store 的宽度参数形式。
⚠️ 坑:名字里的 Unsafe 是认真的——无越界检查、无生命周期管理;持有它的 struct 默认拷贝是浅拷贝,两份指针指向同一块内存,转移还是共享必须显式想清楚。不确定自己为什么需要它时,先用 List。
Python 互操作
from python import Python
def main():
np = Python.import_module("numpy")
a = np.arange(5)
print(a.sum()) # 计算走 NumPy 自己的实现
直接 import 整个 Python 生态,拿到的是 PythonObject,方法属性原样可用。
⚠️ 坑:互操作不是加速器——计算性能取决于被调的 Python 库,加速靠把热点循环改写成 Mojo 原生;在 fn 里操作 Python 对象要持有 GIL(Python.with_gil 一族,具体以官方文档为准);本机需有可用的 Python 环境。
mojo CLI 与 pixi
pixi init hello-mojo && cd hello-mojo # 项目化安装,不污染全局
pixi add mojo # 从 modular-community 渠道装 Mojo
pixi run mojo --version # 所有 mojo 命令走 pixi run 前缀
pixi run mojo run hello.mojo # 运行单文件
pixi run mojo build hello.mojo # 编译成独立可执行文件
pixi run mojo repl # 交互式环境
三条命令覆盖”跑、编、试”:run 执行脚本,build 出二进制,repl 即写即试。
⚠️ 坑:忘了 pixi run 前缀,命中的可能是系统环境甚至没有 mojo;mojo run 的选项要写在文件路径之前;25.x 各版本间 CLI 与标准库 API 都有破坏性变更,升级后先看 changelog 再跑旧代码。
速查表
| 条目 | 一句话用途 | 有什么坑 |
|---|---|---|
| def / fn | 动态模式与严格模式分工 | def 参数是可变副本,fn 参数默认只读(赋值报错) |
| var | 声明可变局部变量 | let 已删除;fn 里必须 var,def 可隐式 |
| struct / @fieldwise_init | 静态类型载体与逐字段构造器 | 无继承;@value 已弃用;没构造器不能 Point(…) |
| trait | 能力标签与泛型约束,替代继承 | 括号漏声明报 trait 错;方法名有版本间改名 |
| alias | 编译期常量,方括号参数的货币 | 只装编译期值;命名演进以官方文档为准 |
| read / mut / owned / out | 参数所有权约定 | 默认 read 改参报错;旧名 borrowed/inout 要换算 |
| 方括号参数 [W: Int] | 编译期参数化函数与 struct | 实参必须编译期已知,运行时值进不来 |
| @parameter if | 编译期分支 | 条件必须是编译期值 |
| raise / try / except | Python 风格错误处理 | fn 抛错必须签名写 raises |
| Int | 固定宽度整数 | 非任意精度;/ 得浮点,// 才整除 |
| Float64 / SIMD | 浮点标量即宽度 1 向量 | 混宽度要显式 cast;Int 不在 SIMD 体系 |
| String | UTF-8 字符串 | 类型体系持续重构,方法名以官方文档为准 |
| List | 动态数组 | 记得 from collections import;拼接用 append |
| Dict | 哈希表 | 缺键抛错,get 兜底习惯不能搬 |
| UnsafePointer | 裸指针 alloc/load/store | 无越界无生命周期;struct 浅拷贝共享指针 |
| python 模块 | import 任意 Python 生态 | 不是加速器;fn 里要 GIL;需本机 Python |
| pixi + mojo CLI | run/build/repl 三件套 | 忘 pixi run 前缀命中错环境;升级先看 changelog |
下一步
条目查完,两个方向:要看这些语法在真实性能场景里怎么组合,读《用 Mojo 加速矩阵乘法》——UnsafePointer、SIMD、parallelize 全部上场;要规划学什么、按什么顺序学,读开山篇的观察结论与本系列的收尾篇《Mojo 学习路线》。与 Python 侧同名概念的系统对照(内置函数那一层),站内已有《Python 内置函数详解》。
版本核实(以官方文档为准):条目以 25.4(2025-06-18 发布)时点的官方文档为基准——参数约定 read/mut/owned/out(25.1 起定名)、@fieldwise_init(@value 弃用)、let 已删除、collections 模块的 List/Dict、SIMD 归约方法、UnsafePointer 的 alloc/load/store、python 模块互操作与 pixi 安装路线均为已发布事实;String 类型体系、trait 方法命名、自定义错误机制在 25.x 内持续演进,具体拼写以 docs.modular.com 当前文档为准。本文未引用 25.5 及以后版本的特性。