Rust: Rust 所有权(Ownership):内存安全的核心三原则

最后更新:2026-08-26

所有权是 Rust 最独特的特性——它让 Rust 在不需要垃圾回收器的情况下保证内存安全。

所有权是 Rust 和其他语言最根本的区别。理解了它,你就理解了 Rust 的设计灵魂。


1. 你将学到


2. 概念图解

100%
flowchart LR
    A["值创建<br>let s = String::from(...)"] --> B["所有者绑定<br>s 拥有该值"]
    B --> C["所有权转移 move<br>let s2 = s; s 失效"]
    C --> D["离开作用域 drop<br>} 自动释放内存"]
    D --> E["内存安全<br>无双重释放、无悬垂指针"]

3. 一个图书馆的故事

(1) 痛苦:书被两个人同时借走了

Tom 管理一家小型图书馆。他遇到了一个经典问题:

"如果一本书一次只能借给一个人就好了……"

(2) Rust 所有权的规则

RUST
fn main() {
    let book = String::from("Rust 编程");  // book 是这本书的所有者
    // let book2 = book;                   // ❌ 如果这样写,book 的所有权转移给了 book2
    // println!("{}", book);               // ❌ book 不能再被使用

    // ✅ 正确的做法:一次只有一个人拥有所有权
    println!("{} 属于 Tom 的图书馆", book);
}  // book 在这里被自动销毁——不需要手动 free

Rust 的所有权规则就像图书馆的"一次只借给一个人"原则:每个值在任意时刻有且只有一个所有者。所有者离开作用域时,值被自动销毁。


4. 所有权三原则

100%
graph TB
    A[所有权三原则] --> B[原则1: 每个值有且只有一个所有者]
    A --> C[原则2: 值离开作用域即被销毁]
    A --> D[原则3: 所有权可以转移(move)]
    B --> E[let s = String::from("hi")]
    C --> F[} 结束时自动调用 drop]
    D --> G[let s2 = s;  // s 不再有效]

(1) 原则详解

原则 说明 类比
唯一所有者 每个值在任意时刻只有一个人"拥有"它 图书馆的书一次只能借给一个人
作用域结束 = 销毁 变量离开作用域时自动调用 drop 借书到期自动归还
所有权转移(move) 赋值或传参时所有权转移,原变量失效 书从一个人手里交到另一个人手里

5. 栈与堆

理解所有权必须先理解栈和堆的区别:

维度 栈(Stack) 堆(Heap)
分配速度 极快(push/pop) 较慢(需找空闲内存)
存储数据 编译时已知大小的数据 大小未知或动态变化的数据
典型类型 i32boolf64、数组 StringVecBox
内存管理 自动(函数调用栈帧) 需要手动或通过所有权管理

(3) Copy vs Clone vs Move 对比

操作 语法 原变量是否有效 性能开销 适用类型
Copy let b = a; 有效 极低(栈按位复制) i32f64boolchar、元组(含Copy类型)
Clone let b = a.clone(); 有效 较高(堆内存分配) StringVec<T>Box<T>
Move let b = a; 失效 零(只复制指针) StringVec<T>Box<T>

如果一个类型实现了 Copy,赋值时自动复制;如果没实现 Copy,赋值时自动移动。需要保留原变量时用 .clone() 显式深拷贝。

RUST
fn main() {
    // 栈上数据:大小固定,Copy 语义
    let x: i32 = 5;        // 栈上分配 4 字节
    let y = x;              // 复制一份,x 和 y 都有效
    println!("x={}, y={}", x, y);  // ✅ 都可用

    // 堆上数据:大小不定,move 语义
    let s1 = String::from("hello");  // s1 在栈上(ptr/len/cap),实际数据在堆上
    let s2 = s1;                     // ❌ 所有权从 s1 转移到 s2
    // println!("{}", s1);            // ❌ 编译错误:s1 已被移动
    println!("{}", s2);              // ✅ s2 是新的所有者
}

6. 所有权示例

▶ 示例 1:move 语义——所有权转移(难度 ⭐⭐)

RUST
// ============================================
// 演示所有权转移:赋值时发生 move
// ============================================

fn main() {
    let s1 = String::from("Rust");
    let s2 = s1;  // s1 的所有权转移到 s2

    // println!("s1: {}", s1);  // ❌ 编译错误!s1 已被移动
    println!("s2: {}", s2);     // ✅ s2 现在是所有者

    // 整数类型是 Copy 的,所以不会 move
    let a = 42;
    let b = a;                 // a 的值被复制给 b
    println!("a: {}, b: {}", a, b);  // ✅ 两者都有效
}

输出:

TEXT 📖 仅展示
s2: Rust
a: 42, b: 42

整型等标量类型实现了 Copy trait,赋值时是复制而不是移动。String 没有实现 Copy,赋值时是移动。移动后原变量失效,这是 Rust 防止"双重释放"的关键设计。


▶ 示例 2:函数调用中的所有权传递(难度 ⭐⭐)

RUST
// ============================================
// 传参和返回值中的所有权转移
// ============================================

fn take_ownership(s: String) {
    println!("拿到所有权: {}", s);
}  // s 在这里被销毁(drop)

fn give_ownership() -> String {
    let s = String::from("新创建的字符串");
    s  // 所有权返回给调用者
}

fn main() {
    let s1 = String::from("hello");

    take_ownership(s1);  // s1 的所有权被移入函数
    // println!("{}", s1);  // ❌ s1 已无效

    let s2 = give_ownership();  // 从函数中获取所有权
    println!("s2: {}", s2);     // ✅ s2 拥有所有权

    // 传进去再传回来
    let s3 = String::from("来回传递");
    let s3_back = takes_and_returns(s3);
    // println!("{}", s3);  // ❌ s3 已被移动
    println!("s3_back: {}", s3_back);  // ✅
}

fn takes_and_returns(s: String) -> String {
    println!("函数内部: {}", s);
    s  // 把所有权返回给调用者
}

输出:

TEXT 📖 仅展示
拿到所有权: hello
s2: 新创建的字符串
函数内部: 来回传递
s3_back: 来回传递

函数调用时,传入的参数所有权被转移给函数参数。函数返回值时,所有权被转移给调用者。这就是"所有权在函数间流动"——和谁拿着钥匙谁就能开门一个道理。


▶ 示例 3:Copy vs Clone 的对比(难度 ⭐⭐⭐)

RUST
// ============================================
// Copy(自动复制) vs Clone(显式克隆)
// ============================================

fn main() {
    // --- Copy 类型:赋值自动复制 ---
    let num1 = 100;
    let num2 = num1;         // 自动复制,num1 仍然有效
    println!("Copy: num1={}, num2={}", num1, num2);  // ✅

    // --- Clone 类型:需要显式调用 .clone() ---
    let s1 = String::from("需要克隆");
    let s2 = s1.clone();     // 显式克隆,s1 仍然有效
    println!("Clone: s1={}, s2={}", s1, s2);  // ✅

    // --- 不 Clone 也不 Copy:只能 move ---
    let v1 = vec![1, 2, 3];
    let v2 = v1;  // move!v1 失效
    // println!("{:?}", v1);  // ❌
    println!("v2: {:?}", v2);  // ✅
}

输出:

TEXT 📖 仅展示
Copy: num1=100, num2=100
Clone: s1=需要克隆, s2=需要克隆
v2: [1, 2, 3]

Copy:赋值时自动按位复制,原始变量仍然有效(适用于栈上数据)。Clone:需要显式调用 .clone() 方法,适用于堆上数据。Clone 的代价更高(需要分配堆内存),所以 Rust 设计成"你必须显式选择克隆"。


▶ 示例 4:函数中所有权的获取与归还模式(难度 ⭐⭐⭐)

RUST
// ============================================
// 常见模式:传进去再传回来(获取+归还)
// ============================================

struct User {
    name: String,
    score: i32,
}

fn boost_score(mut user: User, bonus: i32) -> User {
    user.score += bonus;
    user
}

fn format_user(user: &User) -> String {
    format!("{}: {} 分", user.name, user.score)
}

fn main() {
    let alice = User {
        name: String::from("Alice"),
        score: 80,
    };

    println!("升级前: {}", format_user(&alice));

    let alice = boost_score(alice, 15);
    println!("升级后: {}", format_user(&alice));

    let alice = boost_score(alice, 10);
    println!("再次升级: {}", format_user(&alice));

    let mut bob = User {
        name: String::from("Bob"),
        score: 60,
    };

    let bonus = 20;
    bob.score += bonus;
    println!("Bob 加分后: {}", format_user(&bob));

    let names = vec![alice.name.clone(), bob.name.clone()];
    println!("所有用户: {:?}", names);

    println!("Alice 最终分数: {}", alice.score);
    println!("Bob 最终分数: {}", bob.score);
}

输出:

TEXT 📖 仅展示
升级前: Alice: 80 分
升级后: Alice: 95 分
再次升级: Alice: 105 分
Bob 加分后: Bob: 80 分
所有用户: ["Alice", "Bob"]
Alice 最终分数: 105
Bob 最终分数: 80

结构体的所有权传递与基本类型一致。boost_score 通过参数获取所有权、修改后返回——这是"获取+归还"模式。用引用(&User)可以只读访问而不获取所有权。


▶ 示例 5:所有权与集合的交互(难度 ⭐⭐⭐)

RUST
// ============================================
// Vec、String 与所有权的常见交互模式
// ============================================

fn take_first_word(words: &Vec<String>) -> Option<&str> {
    words.first().map(|s| s.as_str())
}

fn remove_and_return(vec: &mut Vec<i32>, index: usize) -> Option<i32> {
    if index < vec.len() {
        Some(vec.remove(index))
    } else {
        None
    }
}

fn main() {
    let mut words = vec![
        String::from("hello"),
        String::from("rust"),
        String::from("world"),
    ];
    println!("原始向量: {:?}", words);

    if let Some(first) = take_first_word(&words) {
        println!("第一个单词: {}", first);
    }

    let removed = remove_and_return(&mut words, 1);
    println!("移除索引 1: {:?}, 剩余: {:?}", removed, words);

    let mut numbers = vec![10, 20, 30, 40, 50];
    while let Some(val) = numbers.pop() {
        println!("弹出: {}", val);
    }
    println!("向量已空: {:?}", numbers);

    let s1 = String::from("foo");
    let s2 = String::from("bar");
    let combined = s1 + &s2;
    println!("拼接结果: {}", combined);

    let data = vec![String::from("a"), String::from("b")];
    for word in &data {
        println!("借用: {}", word);
    }
    println!("向量仍可用: {:?}", data);

    let data2 = vec![1, 2, 3];
    for num in data2 {
        println!("消费: {}", num);
    }
}

输出:

TEXT 📖 仅展示
原始向量: ["hello", "rust", "world"]
第一个单词: hello
移除索引 1: Some(20), 剩余: ["hello", "world"]
弹出: 50
弹出: 40
弹出: 30
弹出: 20
弹出: 10
向量已空: []
拼接结果: foobar
借用: a
借用: b
向量仍可用: ["a", "b"]
消费: 1
消费: 2
消费: 3

for item in &vec 借用遍历(向量仍可用),for item in vec 消费遍历(向量被 move)。+ 操作符消费左侧 String。pop() 返回 Option<T> 并消费最后一个元素。


❓ 常见问题

Q 为什么 Rust 要设计所有权?不用垃圾回收不是更简单吗?
A 所有权让 Rust 在没有 GC 暂停的前提下保证内存安全。
Q move 之后原变量去哪了?
A 原变量被编译器标记为"已失效"。
Q 哪些类型是 Copy 的?
A 所有标量类型(整数、浮点、布尔、字符)和由它们组成的元组/数组。
Q .clone() 和 move 哪个性能好?
A move 是零开销,clone 需要分配堆内存。
Q 所有权在结构体里怎么工作?
A 结构体整体的所有权规则和单个变量一样。

📖 小节


📝 作业

  1. 难度 ⭐:写一个程序,创建一个 String,把它赋值给另一个变量,然后尝试打印第一个变量——观察编译器报错信息。
  2. 难度 ⭐⭐:写一个函数 fn append_world(s: String) -> String,在字符串末尾追加 " world" 并返回。在 main 中调用并观察所有权如何进出函数。
  3. 难度 ⭐⭐⭐:定义一个函数 fn calculate_length(s: String) -> (String, usize),返回字符串及其长度,同时把所有权还回来。验证调用后原变量可以继续使用。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏