所有权系统

所有权(Ownership)是 Rust 最独特也是最核心的特性。它使得 Rust 能够在没有垃圾回收器的情况下保证内存安全。

什么是所有权?

在 Rust 中,每个值都有一个所有者(owner)。所有权系统遵循三条基本规则:

  1. Rust 中的每个值都有一个所有者
  2. 同一时刻,一个值只能有一个所有者
  3. 当所有者离开作用域时,值将被自动释放

基本示例

让我们通过代码来理解所有权:

fn main() {
    let s1 = String::from("hello");  // s1 是字符串的所有者
    let s2 = s1;                      // 所有权转移给 s2

    // println!("{}", s1);            // ❌ 编译错误!s1 已经无效
    println!("{}", s2);               // ✅ 正确,s2 现在是所有者
}

发生了什么?

当我们执行 let s2 = s1 时:

  • 不是复制数据(这会很昂贵)
  • 而是转移了所有权
  • s1 变为无效,不能再使用
  • s2 成为新的所有者

内存布局

让我们可视化一下 String 在内存中的表示:

栈(Stack)                    堆(Heap)
┌──────────┐
│   s1     │──────┐
├──────────┤      │            ┌─────────┐
│  ptr     │──────┼───────────>│  h      │
│  len: 5  │      │            │  e      │
│  cap: 5  │      │            │  l      │
└──────────┘      │            │  l      │
                  │            │  o      │
┌──────────┐      │            └─────────┘
│   s2     │──────┘
├──────────┤
│  ptr     │──────────────────>(指向同一块堆内存)
│  len: 5  │
│  cap: 5  │
└──────────┘

重要:移动操作后,s1 的指针被失效,防止双重释放(double free)问题。

克隆(Clone)

如果确实需要深拷贝堆上的数据,可以使用 clone 方法:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();  // 完整复制堆上的数据

    println!("s1 = {}, s2 = {}", s1, s2);  // ✅ 都有效
}

⚠️ 注意clone 可能会很昂贵,因为它会复制堆上的所有数据。

栈上的数据:复制(Copy)

对于存储在栈上的简单类型,Rust 会自动复制而不是移动

fn main() {
    let x = 5;
    let y = x;  // 复制值

    println!("x = {}, y = {}", x, y);  // ✅ 都有效
}

这些类型实现了 Copy trait:

  • 所有整数类型(i32, u64 等)
  • 布尔类型 bool
  • 浮点类型(f32, f64)
  • 字符类型 char
  • 元组(如果所有成员都是 Copy 的)

函数与所有权

传递值到函数

将值传递给函数会移动复制,和赋值一样:

fn main() {
    let s = String::from("hello");  // s 进入作用域

    takes_ownership(s);              // s 的值移动到函数
    // println!("{}", s);            // ❌ s 已经无效

    let x = 5;                       // x 进入作用域

    makes_copy(x);                   // x 被复制到函数
    println!("{}", x);               // ✅ x 仍然有效
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
}  // some_string 在这里被释放

fn makes_copy(some_integer: i32) {
    println!("{}", some_integer);
}  // some_integer 在这里离开作用域,无事发生

返回值与所有权

函数也可以转移所有权:

fn main() {
    let s1 = gives_ownership();         // 函数返回值移动到 s1

    let s2 = String::from("hello");     // s2 进入作用域

    let s3 = takes_and_gives_back(s2);  // s2 移入函数,返回值移动到 s3
}

fn gives_ownership() -> String {
    let some_string = String::from("yours");
    some_string  // 返回值移动给调用者
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string  // 返回值移动给调用者
}

为什么需要所有权?

所有权系统解决了几个关键问题:

问题传统语言的解决方案Rust 的方案
内存泄漏垃圾回收器(GC)编译时检查
悬垂指针运行时检查编译时检查
数据竞争锁和同步原语编译时检查
双重释放引用计数编译时检查

核心优势零成本抽象 + 内存安全 + 无需 GC

小结

  • 所有权是 Rust 的核心特性
  • 默认情况下,赋值和传参会移动所有权
  • Copy 类型会被复制而不是移动
  • 使用 clone() 可以显式深拷贝
  • 当所有者离开作用域,值会被自动释放

下一步,我们将学习借用(Borrowing),这是一种在不获取所有权的情况下使用值的方式。