Chapter 04

所有权与内存管理

理解 Mojo 的值语义、borrowed/owned/inout 参数约定,掌握内存安全的核心机制

内存管理基础概念

栈内存 vs 堆内存

栈内存(Stack)
函数调用时自动分配,函数返回时自动释放。速度极快(只需移动栈指针),但大小有限(通常 1-8 MB)。Mojo struct、Int、Float 等值类型默认存放在栈上。
堆内存(Heap)
手动申请(malloc/new)、手动释放(free/delete)或 GC 自动管理的内存区域。大小几乎无限(受物理内存限制),但分配/释放开销较大。Mojo 的 List、String 等动态数据结构在堆上分配。
RAII
Resource Acquisition Is Initialization,资源获取即初始化。一种内存管理模式:在对象构造时获取资源,在对象析构时自动释放。C++ 和 Rust 都使用 RAII,Mojo 也遵循这一模式——通过 __del__ 方法定义析构行为。

值语义 vs 引用语义

这是 Mojo 内存模型的核心概念,理解这对区别是掌握所有权系统的基础:

# 值语义(Value Semantics)
# 赋值时产生独立副本
var a = 42
var b = a    # b 是 a 的副本
b = 100      # 只改变 b
print(a)    # 42(a 不受影响)

# Mojo struct 默认是值语义
var v1 = Vector2D(1.0, 2.0)
var v2 = v1   # 完整复制
v2.x = 99.0  # 只改 v2
print(v1.x)  # 1.0(v1 不变)
# Python 引用语义(参考对比)
# 赋值时共享引用
a = [1, 2, 3]
b = a        # b 指向同一对象
b.append(4) # 通过 b 修改
print(a)   # [1, 2, 3, 4]!
            # a 也被改了
# Python 这种行为常导致难以发现的 bug

三种参数约定:owned、borrowed、inout

核心设计思想

Mojo 函数的每个参数都有明确的所有权约定(ownership convention),通过关键字声明,编译器据此做内存安全检查和优化。

borrowed(默认)
函数借用调用者的值,不拥有它,不能修改它,函数返回后调用者的值依然有效。类似 Rust 的不可变借用 &T。这是 fn 函数参数的默认约定,不需要显式写 borrowed 关键字(写了也没错)。零拷贝开销,最高效。
inout
函数获得参数的可变引用,可以修改调用者的值,类似 Rust 的 &mut T 或 C++ 的引用参数 T&。函数内的修改直接作用于调用者的变量。用于需要就地修改参数的场景(如自增、追加等)。
owned
函数获取所有权,调用者转让其值的所有权。函数可以自由修改、消费或转移该值。调用者在传参后不再拥有该值(如果是 move 语义)。用于函数需要拥有资源的场景(如存储到结构体中)。

三种约定的代码示例

struct BigData:
    var values: List[Float64]

    fn __init__(inout self, size: Int):
        self.values = List[Float64](size)

# 1. borrowed(默认):只读访问,无拷贝
fn compute_sum(data: BigData) -> Float64:
    # data 是借用,不能修改 data
    var total: Float64 = 0.0
    for v in data.values:
        total += v[]
    return total

# 2. inout:可修改引用
fn normalize(inout data: BigData):
    # 可以直接修改 data.values
    var total = compute_sum(data)
    for i in range(len(data.values)):
        data.values[i] = data.values[i] / total

# 3. owned:获取所有权
fn consume_and_process(owned data: BigData) -> Float64:
    # data 的所有权已转移到这个函数
    var result = compute_sum(data)
    # 函数结束,data 在这里被析构
    return result

fn main():
    var d = BigData(1000)

    # 调用 borrowed 函数:d 依然有效
    let s = compute_sum(d)

    # 调用 inout 函数:d 被修改
    normalize(d)

    # 调用 owned 函数:d 的所有权被转移
    let r = consume_and_process(d^)  # ^ 表示显式转移所有权
    # 之后不能再使用 d!
^ 运算符:显式转移所有权

在 Mojo 中,调用 owned 参数的函数时,使用 ^(transfer operator)显式表示"我愿意转让这个值的所有权"。这提高了代码的可读性——当你看到 func(x^) 时,立刻知道 x 之后不能再使用了。

生命周期与析构 __del__

struct ManagedBuffer:
    var ptr: UnsafePointer[Float32]
    var size: Int

    fn __init__(inout self, size: Int):
        self.size = size
        # 分配堆内存
        self.ptr = UnsafePointer[Float32].alloc(size)
        print("分配", size, "个 Float32")

    fn __del__(owned self):
        # 析构函数:对象离开作用域时自动调用
        self.ptr.free()
        print("释放", self.size, "个 Float32")

fn main():
    print("开始")
    {
        var buf = ManagedBuffer(1024)
        print("使用 buf")
        # buf 在这里离开作用域,__del__ 自动调用
    }
    print("结束")
    # 输出:
    # 开始
    # 分配 1024 个 Float32
    # 使用 buf
    # 释放 1024 个 Float32
    # 结束

UnsafePointer:底层内存操作

什么时候使用 UnsafePointer?

UnsafePointer 是 Mojo 中逃逸所有权系统、直接操作裸内存的工具。名字中的 "Unsafe" 是明确警告:使用它需要开发者手动保证内存安全,编译器不再保护你。

合理使用场景:实现高性能数据结构(自定义内存布局)、与 C 库交互(FFI)、SIMD 内存加载/存储、绝对不允许拷贝开销的热路径。

fn unsafe_pointer_demo():
    # 分配 8 个 Float32 的连续内存
    var ptr = UnsafePointer[Float32].alloc(8)

    # 初始化内存(必须手动,否则是垃圾值)
    for i in range(8):
        ptr[i] = Float32(i) * 1.5

    # 读取内存
    for i in range(8):
        print(ptr[i], end=" ")  # 0.0 1.5 3.0 4.5 ...

    # 指针算术:偏移访问
    var mid_ptr = ptr + 4    # 指向第 5 个元素
    print(mid_ptr[0])       # 6.0

    # 必须手动释放!(否则内存泄漏)
    ptr.free()
UnsafePointer 的使用规则

1. 分配后必须初始化,否则读取到垃圾值
2. 使用完毕必须调用 .free(),否则内存泄漏
3. 不要访问已释放的内存(use-after-free)
4. 不要越界访问(buffer overflow)
5. 优先考虑用 Mojo 标准库(List、String 等),只在确实需要时才用 UnsafePointer

Mojo vs Rust 所有权模型对比

特性RustMojo
只读借用&T默认 / borrowed
可变借用&mut Tinout
所有权转移移动语义(默认)owned + ^ 显式转移
生命周期标注必须(复杂场景)无(编译器自动推断)
借用检查器编译期强制执行编译期强制执行
逃逸安全unsafe 块UnsafePointer
析构函数Drop trait__del__ 方法
学习曲线陡峭(尤其生命周期)较平缓(更接近 Python)
本章小结

值语义:Mojo struct 默认值语义,赋值产生副本,避免 Python 式的隐式共享引用导致的 bug。
三种参数约定borrowed(只读借用,默认)、inout(可变引用)、owned(获取所有权),显式表达意图。
__del__ 析构:RAII 模式,对象离开作用域自动调用,确保资源释放。
UnsafePointer:底层裸指针,逃逸编译器保护,需要手动保证安全,仅在性能极端敏感时使用。
与 Rust 对比:Mojo 去掉了 Rust 复杂的生命周期标注,使用更直观的关键字约定,对 Python 背景开发者更友好。