Mojo 所有权与值语义
本篇覆盖:所有权三条规则、参数约定(默认引用 / mut / var + ^)、参数排他性、与 Rust 对比。
⚠️ 重点提醒:Mojo 1.0 的参数约定彻底重做过,旧教程的
borrowed/inout/owned已废弃,现在是「默认不可变引用 /mut/var」。
一、为什么需要所有权
手动管理内存的语言里,多个地方共享同一块内存时,「谁负责释放」是个经典难题——搞错就是 use-after-free、double free、内存泄漏。
Mojo 的解法和 Rust 同源但更温和,三条核心规则:
- 每个值同一时刻只有一个所有者
- 所有者生命周期结束时,值被销毁(析构及时调用)
- 存在引用时,所有者的生命周期被延长(不会出现悬垂引用)
变量拥有它的值,struct 拥有它的字段——所有权关系在编译期完全确定。
二、值语义:赋值即拷贝
Mojo 默认值语义,行为可预测:
def main():
var a = List[Float64]([1.0, 2.0])
var b = a # b 是 a 的拷贝(独立的新值)
b.append(3.0)
print(len(a)) # 2 —— a 不受影响
print(len(b)) # 3对比 Python(引用语义:b = a 后两者指向同一个 list,append 互相可见)。语义差异是 Python 开发者最容易踩的坑。
三、参数约定:函数怎么拿值
函数传参时,「可变性」和「所有权」由参数约定控制。约定关键字写在参数名前面。
1. 默认:不可变引用(零拷贝只读)
def print_list(list: List[Int]):
print(list.__str__())
def main():
var values = [1, 2, 3, 4]
print_list(values) # 函数内只能读,不能改- 函数拿到的是引用而非拷贝——大对象传递零开销
- 类似 C++ 的
const&,但编译器带生命周期检查 Int、Bool等小类型永远寄存器传值(等于拷贝,更高效)
2. mut:可变引用
想让函数修改调用方的值,加 mut:
def mutate(mut l: List[Int]):
l.append(5)
def main():
var values = [1, 2, 3, 4]
mutate(values)
print(values) # [1, 2, 3, 4, 5]mut 的含义一句话:函数内的修改,函数外可见。(替代旧语法的 inout。)
3. var + ^:所有权转移
函数要拿走值的所有权时,参数声明 var,调用方用 ^ 后缀转移:
def consume(var s: String):
print(t"消费掉了: {s}")
def main():
var msg = "重要数据"
consume(msg^) # 所有权移交给 consume
# msg 此后不可再用(未初始化状态),除非重新赋值不加 ^ 时,var 约定会拷贝一份给函数(类型不可拷贝则编译报错);加了 ^ 才是真正的零拷贝转移。
四、参数排他性:mut 的防呆设计
mut 引用有排他性约束:同一值不能同时以 mut 和其他任何引用形式出现在参数里:
def append_twice(mut s: String, other: String):
s += other
s += other
def main():
var my_string = "o"
append_twice(my_string, my_string) # 编译错误!
# my_string 同时作为 mut 和不可变引用传入,被拒绝
# 正确做法:显式拷贝一份
var other = my_string
append_twice(my_string, other) # OK这防掉了「别名导致的隐式串改」这类隐蔽 bug,也让编译器有更多优化空间。注意 Int、Bool 等平凡小类型不受此限制(它们总是按拷贝传)。
五、和 Rust 对比
| Rust | Mojo | |
|---|---|---|
| 所有权模型 | 所有权 + 借用检查 | 同源思想,检查更宽松 |
| 只读传参 | &T(调用方要写 &) | 默认行为(调用方无感) |
| 可变传参 | &mut T | mut 关键字(参数声明处) |
| 转移所有权 | move / 默认移动 | var 参数 + ^ 后缀 |
| 小类型传值 | 移动语义 | 寄存器传值(等价拷贝) |
| 学习曲线 | 陡(生命周期标注) | 平缓(无需标注,编译器推断) |
一句话总结:Mojo 拿到了 Rust 大部分内存安全收益,代价是少量运行时检查和稍弱的优化上限,换来的是 Python 级的上手体验。
六、实践建议
- 只读就什么都不加——默认约定已是最优路径
- 要改入参就
mut——语义清晰,还能防别名 bug - 大对象「用完即弃」才用
var+^——其余场景默认拷贝即可 - 自定义 struct 里若持有堆资源(如裸指针),记得实现正确的析构与拷贝/移动构造——日常用
List/String等标准类型则不用操心
七、小结
- 三条规则:单一所有者 / 到期销毁 / 引用延长寿命
- 值语义默认,
b = a是拷贝(与 Python 相反) - 参数约定:默认只读引用、
mut可变、var+^转移 mut有排他性,别名 bug 编译期拦截
下一篇:编译期参数与 comptime 元编程。