Skip to content
当前页大纲

Mojo 所有权与值语义

本篇覆盖:所有权三条规则、参数约定(默认引用 / mut / var + ^)、参数排他性、与 Rust 对比。

⚠️ 重点提醒:Mojo 1.0 的参数约定彻底重做过,旧教程的 borrowed / inout / owned 已废弃,现在是「默认不可变引用 / mut / var」。

一、为什么需要所有权

手动管理内存的语言里,多个地方共享同一块内存时,「谁负责释放」是个经典难题——搞错就是 use-after-free、double free、内存泄漏。

Mojo 的解法和 Rust 同源但更温和,三条核心规则:

  1. 每个值同一时刻只有一个所有者
  2. 所有者生命周期结束时,值被销毁(析构及时调用)
  3. 存在引用时,所有者的生命周期被延长(不会出现悬垂引用)

变量拥有它的值,struct 拥有它的字段——所有权关系在编译期完全确定。

二、值语义:赋值即拷贝

Mojo 默认值语义,行为可预测:

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. 默认:不可变引用(零拷贝只读)

mojo
def print_list(list: List[Int]):
    print(list.__str__())

def main():
    var values = [1, 2, 3, 4]
    print_list(values)      # 函数内只能读,不能改
  • 函数拿到的是引用而非拷贝——大对象传递零开销
  • 类似 C++ 的 const&,但编译器带生命周期检查
  • IntBool 等小类型永远寄存器传值(等于拷贝,更高效)

2. mut:可变引用

想让函数修改调用方的值,加 mut

mojo
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,调用方用 ^ 后缀转移:

mojo
def consume(var s: String):
    print(t"消费掉了: {s}")

def main():
    var msg = "重要数据"
    consume(msg^)       # 所有权移交给 consume
    # msg 此后不可再用(未初始化状态),除非重新赋值

不加 ^ 时,var 约定会拷贝一份给函数(类型不可拷贝则编译报错);加了 ^ 才是真正的零拷贝转移。

四、参数排他性:mut 的防呆设计

mut 引用有排他性约束:同一值不能同时以 mut 和其他任何引用形式出现在参数里:

mojo
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,也让编译器有更多优化空间。注意 IntBool 等平凡小类型不受此限制(它们总是按拷贝传)。

五、和 Rust 对比

RustMojo
所有权模型所有权 + 借用检查同源思想,检查更宽松
只读传参&T(调用方要写 &默认行为(调用方无感)
可变传参&mut Tmut 关键字(参数声明处)
转移所有权move / 默认移动var 参数 + ^ 后缀
小类型传值移动语义寄存器传值(等价拷贝)
学习曲线陡(生命周期标注)平缓(无需标注,编译器推断)

一句话总结:Mojo 拿到了 Rust 大部分内存安全收益,代价是少量运行时检查和稍弱的优化上限,换来的是 Python 级的上手体验

六、实践建议

  1. 只读就什么都不加——默认约定已是最优路径
  2. 要改入参就 mut——语义清晰,还能防别名 bug
  3. 大对象「用完即弃」才用 var + ^——其余场景默认拷贝即可
  4. 自定义 struct 里若持有堆资源(如裸指针),记得实现正确的析构与拷贝/移动构造——日常用 List/String 等标准类型则不用操心

七、小结

  • 三条规则:单一所有者 / 到期销毁 / 引用延长寿命
  • 值语义默认,b = a 是拷贝(与 Python 相反)
  • 参数约定:默认只读引用、mut 可变、var + ^ 转移
  • mut 有排他性,别名 bug 编译期拦截

下一篇:编译期参数与 comptime 元编程。

MIT License.