Rust 入门教程(五):所有权与借用(核心机制)

Rust 最大的特色,不是宏,不是性能,而是它独有的 所有权系统(Ownership System)
这个机制帮助我们在**没有垃圾回收器(GC)**的情况下,依然保证内存安全。

今天我们就来理解 Rust 的灵魂:
✅ 所有权规则
✅ 移动语义 vs 复制语义
✅ 引用与借用(&、&mut)
✅ 生命周期初探


1️⃣ 所有权机制详解

在 Rust 中,每一个值(value)都有一个所有者(owner),并且在任意时刻,值只能有一个所有者。

当所有者离开作用域时,值就会被自动释放(drop),这就是 Rust 内存安全的根基。

规则总结

  1. 每个值都有一个变量作为它的所有者
  2. 同一时间只能有一个所有者
  3. 当所有者离开作用域时,值会被释放

示例

fn main() {{let s = String::from("hello"); // s 拥有这个字符串println!("{}", s);} // s 离开作用域,内存被释放
}

对比 C/C++:你得手动 free() 或者调用析构函数。
对比 Java/Go:由垃圾回收器自动回收。
在 Rust:所有权 + 作用域规则自动帮你管理内存。


2️⃣ 移动语义 vs 复制语义

Rust 区分 移动(move)复制(copy) 两种语义。

移动语义(Move)

当把一个变量赋值给另一个变量时,所有权会转移

fn main() {let s1 = String::from("hello");let s2 = s1; // s1 的所有权移动到 s2// println!("{}", s1); // ❌ 错误:s1 已经失效println!("{}", s2);
}

为什么?因为 String 存在堆内存,直接复制指针会导致“双重释放”错误,所以 Rust 选择转移所有权。


复制语义(Copy)

对于存储在栈上的简单数据类型(整数、布尔、浮点数等),Rust 使用 复制语义

fn main() {let x = 5;let y = x; // x 被复制,而不是转移println!("x = {}, y = {}", x, y); // ✅ x 依然有效
}

哪些类型会自动 Copy

  • 基本数值类型(i32, u64, f64...)
  • bool、char
  • 元组(如果里面的元素都实现了 Copy)

3️⃣ 引用与借用(&、&mut)

有时我们并不想转移所有权,只是想临时使用某个值,这就需要 引用(reference)


不可变引用(&)

fn main() {let s = String::from("hello");let r1 = &s;let r2 = &s;println!("r1 = {}, r2 = {}", r1, r2); // ✅ 可以有多个不可变引用
}

规则:

  • 可以同时存在多个不可变引用
  • 但不能和可变引用共存

可变引用(&mut)

fn main() {let mut s = String::from("hello");let r = &mut s;r.push_str(", world");println!("{}", r);
}

规则:

  • 同一作用域中,只能有一个可变引用
  • 避免数据竞争(data race)

错误示例:

fn main() {let mut s = String::from("hi");let r1 = &mut s;let r2 = &mut s; // ❌ 同时两个可变引用
}

4️⃣ 生命周期初探

生命周期(lifetime) 是 Rust 用来保证引用有效性的机制。
你可以理解为:编译器在帮你追踪引用的作用域

示例

fn main() {let r;{let s = String::from("hello");r = &s; // ❌ s 在这里被释放,r 将变成悬垂引用}// println!("{}", r);
}

Rust 编译器会拒绝编译这段代码,因为它能推导出 r 的生命周期比 s 长,存在安全风险。


生命周期标注(了解)

在函数中,有时需要显式标注生命周期,告诉编译器不同引用之间的关系。

fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {if s1.len() > s2.len() {s1} else {s2}
}

这里的 'a 是生命周期参数,表示 s1s2 的生命周期至少要和返回值一样长。


📌 总结

今天我们学习了 Rust 的核心机制:

  1. 所有权:每个值有唯一所有者,作用域结束时释放
  2. 移动语义 vs 复制语义:堆数据转移,栈数据复制
  3. 引用与借用& 不可变借用,&mut 可变借用(不能混用)
  4. 生命周期:编译器追踪引用的有效范围,避免悬垂引用