基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

Rust 的所有权(Ownership)规则

知识点图片

Rust 的 所有权(Ownership) 是该语言最核心的特性,也是 Rust 能够在没有垃圾回收(GC)的情况下保证内存安全的关键机制。

简单来说,所有权是一套编译时(Compile-time)检查的规则。如果违反了这些规则,程序将无法通过编译。

以下是 Rust 所有权系统的核心内容:


1. 三大核心规则 (The Three Rules)

这是必须死记硬背的铁律:

  1. Rust 中的每一个值都有一个被称为其“所有者”(Owner)的变量。
  2. 值在任一时刻有且只有一个所有者。
  3. 当所有者(变量)离开作用域(Scope)时,这个值将被丢弃(Drop)。

2. 规则详解与示例

为了理解这些规则,我们需要区分栈(Stack)堆(Heap)上的数据。

A. 作用域规则 (Scope)

当变量离开代码块 { ... } 时,Rust 会自动调用 drop 函数来释放内存。

plaintext
{                      // s 在这里无效,它还没声明
    let s = "hello";   // s 在这里有效
    // 使用 s
}                      // 此作用域结束,s 不再有效

B. 移动 (Move) —— 针对堆数据

这是 Rust 与 C++ 或 Python 最不同的地方。

对于复杂类型(如 String),数据存储在堆上。当你把一个变量赋值给另一个变量时,所有权会发生转移

plaintext
let s1 = String::from("hello");
let s2 = s1; 

// println!("{}, world!", s1); // ❌ 报错!s1 已经失效了
println!("{}, world!", s2);    // ✅ 正确
  • 发生了什么?
    • s1 指向堆上的 "hello"。
    • 当执行 let s2 = s1; 时,Rust 没有复制堆上的数据(深拷贝),而是复制了指针。
    • 关键点: 为了保证内存安全(避免二次释放 double-free),Rust 规定 s1 不再有效。这被称为 移动(Move)

C. 克隆 (Clone)

如果你确实想要深度复制堆上的数据,必须显式调用 .clone()

plaintext
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝,堆上现在有两份 "hello"

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

D. 拷贝 (Copy) —— 针对栈数据

对于简单类型(如整数、浮点数、布尔值、字符),它们的大小固定且存储在上。

plaintext
let x = 5;
let y = x;

println!("x = {}, y = {}", x, y); // ✅ 两个都有效
  • 为什么 x 没失效?
    • 因为整数实现了 Copy trait(特征)。
    • 对于这些类型,赋值操作是直接在栈上进行位拷贝,速度极快,不需要“移动”所有权。

3. 所有权与函数

将值传递给函数,语义上等同于给变量赋值。这意味着所有权会转移(除非是 Copy 类型)。

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

    takes_ownership(s);             // s 的值移动到了函数里
                                    // ... 所以在这里 s 不再有效

    // println!("{}", s);           // ❌ 报错!s 已经被拿走了

    let x = 5;                      // x 进入作用域
    makes_copy(x);                  // x 是 Copy 类型
                                    // ... 所以 x 在这里依然有效
}

fn takes_ownership(some_string: String) { // some_string 进入作用域
    println!("{}", some_string);
} // 这里 some_string 离开作用域,`drop` 被调用,内存释放

fn makes_copy(some_integer: i32) {
    println!("{}", some_integer);
}

4. 借用 (Borrowing) —— 引用

如果每次使用变量都要转移所有权再转移回来(通过返回值),会非常麻烦。Rust 提供了 引用(References),使用 & 符号。

借用允许你使用一个值,但不获取其所有权。

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

    let len = calculate_length(&s1); // 传递引用,不转移所有权

    println!("The length of '{}' is {}.", s1, len); // ✅ s1 依然有效
}

fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
    s.len()
} // 这里 s 离开作用域,但因为它没有所有权,所以什么也不会发生(不释放内存)

引用的规则(借用规则)

这也是所有权系统的一部分:

  1. 在任意给定时间,你要么只能有一个可变引用 (&mut T),要么只能有任意数量的不可变引用 (&T)。
  2. 引用必须总是有效的(不能有悬垂指针)。

总结

Rust 的所有权规则是为了解决以下问题:

  • 内存泄漏:通过作用域规则自动释放。
  • 二次释放 (Double Free):通过“移动”语义,确保只有一个所有者负责释放。
  • 数据竞争 (Data Race):通过借用规则(读写互斥),在编译阶段杜绝多线程数据竞争。

虽然刚开始学习时会觉得编译器在“刁难”你(Fighting the borrow checker),但一旦习惯,它能极大地提高代码的安全性和质量。

00:00
00:00