长安的 Rust 入门教程长安的 Rust 入门教程
首页
基础教程
进阶内容
Rust 官网
编程指南
首页
基础教程
进阶内容
Rust 官网
编程指南
  • 基础教程

    • 📚 基础教程
    • 第1章 - 认识 Rust
    • 第2章 - 安装 Rust
    • 第3章 - Hello World
    • 第4章 - 变量与可变性
    • 第5章 - 数据类型
    • /guide/06-functions.html
    • 第7章 - 控制流
    • 第8章 - 所有权
    • 第9章 - 引用与借用
    • 第10章 - 结构体
    • 第11章 - 枚举
    • 第12章 - 模式匹配
    • 第13章 - 错误处理
    • 第14章 - 集合类型
    • 第15章 - 模块系统
  • 实战项目

    • 第16章 - 实战项目:猜数字游戏
    • 第17章 - 实战项目:待办事项 CLI
    • 第18章 - 实战项目:简单 HTTP 服务器

第8章 - 所有权

嗨,朋友!我是长安。

终于来到这一章了!所有权(Ownership)是 Rust 最核心、最独特的概念,也是 Rust 能保证内存安全的秘密武器。

我知道你可能听说过"所有权很难理解",但别担心,我会用最简单的方式,用生活中的例子来解释给你听。

理解了所有权,你就理解了 Rust 的精髓!

🤔 为什么需要所有权?

在讲所有权之前,我们先理解一个问题:内存管理。

不同语言的内存管理方式

Python、Java、JavaScript:

  • 使用垃圾回收器(Garbage Collector, GC)
  • 程序运行时会自动清理不用的内存
  • 优点:程序员不用操心
  • 缺点:GC 会占用性能,有时会导致程序暂停

C、C++:

  • 手动管理内存
  • 程序员要自己分配(malloc)和释放(free)内存
  • 优点:性能高,完全控制
  • 缺点:容易出错(忘记释放、重复释放、悬垂指针等)

Rust:

  • 通过所有权系统在编译时管理内存
  • 优点:既安全又高效,没有 GC,没有手动管理的麻烦
  • 缺点:需要学习新概念(但非常值得!)

长安说

想象一下:

  • Python/Java 就像住酒店,有人帮你打扫房间(GC),但你要付钱(性能开销)
  • C/C++ 就像自己租房,自己打扫,但可能会忘记倒垃圾(内存泄漏)
  • Rust 就像有个智能机器人(编译器),在你入住前就检查你的打扫计划是否合理,确保你不会忘记任何事情!

📖 所有权三大规则

Rust 的所有权系统只有三条简单的规则:

  1. 每个值都有一个所有者(owner)
  2. 一个值同时只能有一个所有者
  3. 当所有者离开作用域,值会被自动清理

就这么简单!让我们逐条理解。

🎯 规则1:每个值都有一个所有者

fn main() {
    let s = String::from("hello");  // s 是这个字符串的所有者
}

这里:

  • String::from("hello") 创建了一个字符串
  • s 成为了这个字符串的所有者

长安说

所有者就像是这个值的"主人"。你可以把值想象成一只宠物狗,s 就是狗的主人。

🔒 规则2:一个值同时只能有一个所有者

这是最重要的规则!看这个例子:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // 所有权从 s1 转移到了 s2
    
    println!("{}", s1);  // ❌ 错误!s1 不再有效
}

编译器会报错:

error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:20
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`
3 |     let s2 = s1;
  |              -- value moved here
4 |
5 |     println!("{}", s1);
  |                    ^^ value borrowed here after move

发生了什么?

当你写 let s2 = s1 时,所有权从 s1 转移(move)到了 s2。此时:

  • s2 成为了字符串的新主人
  • s1 不再有效,不能再使用

长安提醒

这和其他语言非常不同!在 Python 或 Java 中,这种赋值会创建一个引用,两个变量都能用。但在 Rust 中,所有权会转移。

为什么要这样设计?

想象一下,如果两个变量都能拥有同一块内存:

  1. 两个变量都离开作用域
  2. 两个都尝试释放这块内存
  3. 双重释放(double free)错误!程序崩溃或安全漏洞

Rust 通过所有权规则,在编译时就杜绝了这个问题!

生活例子

长安说

想象你有一本书(值),一开始你是主人(s1)。

你把书送给了朋友(let s2 = s1),现在朋友是主人(s2)。

你(s1)就不能再看这本书了,因为书已经不属于你了!

Rust 的规则就是这么直接:一个东西只能有一个主人。

🔄 如何保留原始变量?

如果你想让 s1 继续有效,有两个办法:

方法1:克隆(Clone)

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();  // 深拷贝,创建一个副本
    
    println!("s1 = {}", s1);  // ✅ s1 依然有效
    println!("s2 = {}", s2);  // ✅ s2 也有效
}

clone() 会创建一个完整的副本,s1 和 s2 各自拥有自己的数据。

注意

克隆会复制数据,如果数据很大,可能会比较慢。只在真正需要两份数据时才用。

方法2:使用引用(下一章会详细讲)

fn main() {
    let s1 = String::from("hello");
    let s2 = &s1;  // 借用,不转移所有权
    
    println!("s1 = {}", s1);  // ✅ 都有效
    println!("s2 = {}", s2);  // ✅
}

📦 栈(Stack)vs 堆(Heap)

为什么 i32 类型不会移动所有权,而 String 会呢?

fn main() {
    let x = 5;
    let y = x;  // 没有移动所有权!
    
    println!("x = {}", x);  // ✅ x 依然有效
    println!("y = {}", y);  // ✅ y 也有效
}

这是因为 Rust 区分了两种数据存储方式:

栈(Stack)

  • 存储固定大小的数据
  • 非常快
  • 数据在编译时大小已知

存储在栈上的类型:

  • 整数:i32, u64 等
  • 浮点数:f32, f64
  • 布尔值:bool
  • 字符:char
  • 元组(如果元素都在栈上)

这些类型实现了 Copy trait,赋值时会自动复制,不会移动所有权。

堆(Heap)

  • 存储大小不固定的数据
  • 相对较慢
  • 需要在运行时分配内存

存储在堆上的类型:

  • String
  • Vec<T>(向量)
  • Box<T>(智能指针)

这些类型赋值时会移动所有权。

长安说

为什么区别对待?

  • 栈上的数据很小(比如一个整数只有 4 字节),复制很快,所以就直接复制
  • 堆上的数据可能很大(字符串可能有几 MB),复制很慢,所以用移动所有权的方式,避免不必要的复制

🚪 规则3:离开作用域,值被清理

fn main() {
    {
        let s = String::from("hello");  // s 进入作用域
        println!("{}", s);              // s 在这里有效
    }  // s 离开作用域,内存被自动释放
    
    // println!("{}", s);  // ❌ 错误!s 已经不存在了
}

当变量离开它的作用域(scope,就是那对大括号 {})时,Rust 会自动调用 drop 函数清理内存。

长安说

这就像你下班回家,离开办公室时,灯会自动关掉。你不需要手动关灯,Rust 会帮你做!

📝 函数与所有权

函数参数会转移所有权

fn main() {
    let s = String::from("hello");
    
    takes_ownership(s);  // s 的所有权转移给函数
    
    // println!("{}", s);  // ❌ 错误!s 已经无效了
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
}  // some_string 离开作用域,内存被释放

函数返回值会转移所有权

fn main() {
    let s1 = gives_ownership();  // 函数返回值的所有权转移给 s1
    println!("{}", s1);  // ✅ 可以使用
}

fn gives_ownership() -> String {
    let some_string = String::from("hello");
    some_string  // 返回,所有权转移给调用者
}

实际应用:计算字符串长度

fn main() {
    let s1 = String::from("hello");
    
    let (s2, len) = calculate_length(s1);
    
    println!("字符串 '{}' 的长度是 {}", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();
    (s, length)  // 把 s 的所有权返回,避免被释放
}

但这样写很麻烦!每次都要把所有权转来转去。

更好的方法:使用引用(下一章会讲)

fn main() {
    let s1 = String::from("hello");
    
    let len = calculate_length(&s1);  // 传递引用,不转移所有权
    
    println!("字符串 '{}' 的长度是 {}", s1, len);  // s1 依然有效!
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

💡 小结

在这一章,我们学会了 Rust 最核心的概念——所有权:

三大规则:

  1. 每个值都有一个所有者
  2. 一个值同时只能有一个所有者
  3. 当所有者离开作用域,值会被自动清理

核心要点:

  • 所有权会转移(move),转移后原变量失效
  • 栈上的数据(如 i32)会自动复制,不会转移所有权
  • 堆上的数据(如 String)会转移所有权
  • 函数参数和返回值也会转移所有权
  • 可以用 clone() 创建副本,或者用引用(下一章)

长安的建议

所有权是 Rust 的核心,一开始可能会觉得很繁琐,经常跟编译器"打架"。但相信我,一旦理解了,你会发现:

  1. 你写的代码几乎没有内存 bug
  2. 你对内存管理有了更深的理解
  3. 这些知识对学习其他语言也很有帮助

如果你现在还没完全理解,没关系! 继续往下学,多写代码,慢慢就会有"顿悟"的时刻。

🚀 下一步

所有权的概念很强大,但每次都转移所有权会很麻烦。

下一章,我们会学习引用与借用,这样你就可以"借用"数据,而不需要转移所有权了!

第9章 - 引用与借用 →

💪 练习题

用代码验证这些概念:

  1. 创建一个字符串,赋值给另一个变量,尝试打印原变量,观察编译错误
  2. 使用 clone() 复制一个字符串,确认两个变量都能使用
  3. 创建一个函数,接收一个 String 参数,打印它,然后在 main 中调用这个函数,观察所有权转移
  4. 创建一个整数,赋值给另一个变量,确认两个都能打印(体验 Copy trait)
答案示例
fn main() {
    // 练习1
    let s1 = String::from("hello");
    let s2 = s1;
    // println!("{}", s1);  // 编译错误
    
    // 练习2
    let s3 = String::from("world");
    let s4 = s3.clone();
    println!("s3 = {}, s4 = {}", s3, s4);  // 都能用
    
    // 练习3
    let s5 = String::from("rust");
    print_string(s5);
    // println!("{}", s5);  // 编译错误,所有权已转移
    
    // 练习4
    let x = 5;
    let y = x;
    println!("x = {}, y = {}", x, y);  // 都能用,因为 i32 实现了 Copy
}

fn print_string(s: String) {
    println!("{}", s);
}
最近更新: 2025/12/26 18:01
Contributors: 王长安
Prev
第7章 - 控制流
Next
第9章 - 引用与借用