要点
前置阅读是开山篇:Mojo 的定位、pixi 安装路线与 Python 互操作全景已在那边讲过,本篇只讲语言核心。截至本文(2025 年 6 月下旬),Mojo 最新版是 6 月 18 日发布的 25.4,语言仍在快速演进。”长得像 Python 的系统语言”这句话里,藏着三处最容易按 Python 心智误读的地方:两套函数声明(def/fn)怎么分工、编译期与运行时是两个世界(struct/SIMD/alias)、所有权写在参数签名里(read/mut/owned)。本文按这三条线过掉高频误解,所有示例以 25.4 时点的语法为准。
def 与 fn:两套声明不是冗余,是分工
Mojo 同时保留 Python 式的 def 和严格模式 fn。这不是过渡期的兼容措施,而是刻意设计:脚本和胶水代码用 def 保持 Python 手感,性能关键路径用 fn 换来编译期的全部能力。两者的差异是硬性的,不是风格偏好:
def total(xs): # def:动态模式
t = 0.0 # 变量可以隐式声明,像 Python
for x in xs:
t += x
return t
fn total_strict(xs: List[Float64]) -> Float64: # fn:严格模式
var t = 0.0 # 变量必须用 var 显式声明
for x in xs:
t += x
return t
两条关键差异:def 的参数是可变副本——函数内随便改,不影响调用者,值语义符合直觉;fn 的参数默认是只读借用(read)——你拿到的是原值的引用,语言规定你只能读不能写。在 fn 里对参数赋值会直接编译报错:
fn bump(x: Int) -> Int:
x += 1 # 编译错误:fn 参数默认按 read 借用,只读
return x
反模式点名:报错后随手把参数改成 owned 来”消掉错误”。owned 的语义是接管所有权,调用方交出这个值——为了改一个整数就转移所有权,是把语义工具当报错消除器用。正确动作是想清楚意图:只读就保持默认,要改写就标 mut,见下一节。
参数约定:所有权写在签名里
fn 的每个参数可以带一个约定(argument convention),告诉编译器这个参数怎么进入函数体。这组关键字 2025 年起(25.1 起)统一为 read/mut/owned,构造器里的 self 用 out:
| 约定 | 语义 | 典型场景 |
|---|---|---|
| (默认,read) | 只读借用,函数内不可改,无拷贝开销 | 绝大多数只读参数 |
| mut | 可变借用,改动对调用者可见 | 就地修改:填充、累加、交换 |
| owned | 接管所有权,调用方交出值 | 消耗值、转移缓冲区、链式变换 |
| out | 结果参数,函数负责把值”写出来” | __init__(out self) 与返回值复用 |
fn bump(mut counter: Int): # mut:可变借用
counter += 1 # 改动写回调用者的变量
fn main():
var n = 3
bump(n)
print(n) # 4
读旧教程要会换算:24.x 时代的 borrowed 就是现在的默认 read,inout 现在叫 mut。还要意识到这组命名本身仍在打磨期——2025 年内已经换过一轮,具体拼写以官方文档为准。
struct:静态类型的载体,不是 Python class
struct 是 Mojo 的静态类型载体:字段与方法在编译期确定,内存布局与 C 结构体同量级。它和 Python class 有两条本质差异——没有继承,字段必须标类型。方法里的 self 同样遵守参数约定:要改字段就把 self 标成 mut,只读方法保持默认借用:
@fieldwise_init # 自动生成逐字段构造器(旧教程的 @value 是它的前身,已弃用)
struct Accumulator:
var total: Float64
var count: Int
fn add(mut self, v: Float64): # 要写字段,self 必须是 mut
self.total += v
self.count += 1
fn mean(self) -> Float64: # 只读方法,self 用默认借用
if self.count == 0:
return 0.0
return self.total / self.count
fn __init__(out self): # out self:构造器负责把 self 写出来
self.total = 0.0
self.count = 0
fn main():
var acc = Accumulator()
acc.add(1.5)
acc.add(2.5)
print(acc.mean()) # 2.0
复用不靠继承靠什么?靠 trait:结构体名后括号里声明遵循哪些 trait(如 Copyable、Movable、Stringable),并实现对应方法。哪些 trait 需要显式声明、哪些自动继承,25.x 还在调整,以官方文档为准。反模式点名:拿 Python class 的心智给 struct 找”父类”——Mojo 没有这条通路,强行模仿只会写出既没继承又没 trait 的四不像。
SIMD:连标量都是向量
Mojo 最”系统语言”的一面在这里:浮点标量不是基本类型,而是宽度为 1 的 SIMD 向量的别名——Float64 即 SIMD[DType.float64, 1]。向量宽度是编译期参数,写在方括号里:
fn main():
var a = SIMD[DType.float32, 4](1.0, 2.0, 3.0, 4.0)
var b = SIMD[DType.float32, 4](10.0, 20.0, 30.0, 40.0)
print(a * b) # 逐通道相乘,一条指令干四个
print(a.reduce_add()) # 归约成一个标量
print(a[2]) # 取单个通道
这个设计的意义在于:向量化不是”调用某个加速库”,而是把类型宽度从 1 改成 4 或 8 的参数化改写。同时别误读——Int 是独立的整数类型,不在 SIMD 体系里,浮点才有这个别名待遇。
alias 与 @parameter if:把计算搬进编译期
alias 声明编译期常量:使用处直接替换为编译期求值结果。@parameter if 是编译期分支:条件在编译期定型后,不成立的那一支整体消失,运行时不存在这次判断:
alias WIDTH = 4 # 编译期常量,不是运行时变量
fn chosen() -> Int:
@parameter if WIDTH > 2:
return WIDTH # 编译期分支:运行时没有这个 if
else:
return 1
fn main():
print(chosen()) # 4
反模式点名:用 var 装向量宽度、矩阵形状这类”结构性数值”。SIMD 的宽度参数、struct 的参数化(如 SIMD[DType.float64, W])只接受编译期值,var 装的值进不了方括号。运行时数据用 var,编译期形状用 alias——这个分界线贯穿 Mojo 全部元编程。官方仓库里关于 alias 命名的讨论已挂了多年,语义方向不变、拼写可能变,以官方文档为准。
控制流与错误处理:这部分真的像 Python
前几节都是”像 Python 但不一样”,控制流与错误处理是少数可以直接照搬 Python 直觉的地方:if/elif/else、while、for ... in、break/continue、raise 与 try/except 全部在场,条件不写括号,代码块用缩进。差异集中在两处:循环对象必须是可迭代的 Mojo 类型(range、List、Dict 都行),以及 fn 里抛错要在签名声明 raises:
| 构造 | 主场 | 一句话边界 |
|---|---|---|
| if / elif / else | 布尔分支 | 与 Python 语义一致,条件不用括号 |
| while | 不定次数循环 | 条件是运行时布尔,别和 @parameter if 混为一谈 |
| for … in | 遍历可迭代对象 | range(start, stop, step) 三参形式可用 |
| raise / try / except | 错误传播与兜底 | fn 抛错必须签名写 raises;def 可隐式 |
fn find_first_gt(xs: List[Float64], t: Float64) raises -> Int:
for i in range(len(xs)):
if xs[i] > t:
return i
raise Error("no element above threshold")
这节的实用结论是:从 Python 迁移脚本骨架(读循环、分支、报错)几乎零成本,真正的学习成本全部集中在参数约定、struct 与编译期那一层——这也正是 Mojo 与 Python 分工的意义,胶水部分别浪费精力重新学。
下一步
语法骨架过完了。要一份能查的索引(每个关键字、常用类型一条:语义、最小示例、坑),读《Mojo 基础知识参考字典》;要把这些语法用到真实场景——按”朴素实现到 SIMD 并行”四步加速矩阵乘法的可复现实验设计——读《用 Mojo 加速矩阵乘法》。对照着理解 def/class 这组差异的 Python 侧,可看站内《Python 函数与面向对象》。
版本核实(以官方文档为准):Mojo 25.4 发布于 2025-06-18(YY.N 版本方案);本文涉及的 def/fn 双模式、参数约定 read/mut/owned 与构造器 out(25.1 起定名,24.x 的 borrowed/inout 为旧名)、@fieldwise_init(@value 已弃用)、struct 无继承、trait 括号声明、Float64 为宽度 1 的 SIMD 别名、alias 与 @parameter if、fn 抛错需签名声明 raises,均为该时点已发布的语言事实;trait 自动继承的具体范围与 alias 的后续命名演进仍在调整中,具体以 docs.modular.com 当前文档为准。