learning-rust/part3/所有权关键概念梳理.md

7.6 KiB
Raw Blame History

Rust 所有权关键概念梳理

基于练习题作答中的误区整理,配合纠正说明。


1. 三条核心规则

规则 说明
每个值有且只有一个所有者 变量绑定 = 所有权
同一时刻只能有一个可变引用,或任意多个不可变引用 读写互斥
所有者离开作用域,值被 drop RAII 式自动释放

2. Move所有权转移

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

常见误区: 认为 move 之后还能继续用原变量。堆数据(StringVecBox 等)的赋值/传参默认是 move源变量立即失效。


3. Copy 类型(自动复制)

let x = 42;
let y = x;            // i32 实现了 Copy自动复制
println!("{}", x);    // ✅ x 仍然有效

实现了 Copy 的类型(i32boolf64char&T 等)赋值时会自动复制,不发生 move。 注意: 包含堆数据的类型(StringVec 等)不实现 Copy


4. Clone显式深拷贝

let s1 = String::from("hello");
let s2 = s1.clone();  // 显式深拷贝,两个变量各自独立
println!("{} {}", s1, s2); // ✅

与 Copy 的区别clone 是显式的、可能有开销Copy 是隐式的、按位复制、无开销。


5. 引用Borrow—— 不转移所有权 ⚠️ 关键

& 就是借用,从!不!转!移!所!有!权!

let x = String::from("hello");
let y = &x;           // y 只是借用了 xx 仍然是所有者
println!("{}", x);    // ✅ x 仍然可用
println!("{}", y);    // ✅ y 可以通过引用读取

原答误以为 let y = &x 会转移所有权 → 完全错误。与下面对比:

let x = String::from("hello");
let y = x;            // ← 没有 &,这是 movex 失效
let z = &x;           // ← 有 &引用x 仍在
写法 行为
let y = x; move所有权转移
let y = &x; borrow借用x 不动
let y = x.clone(); 深拷贝,各自独立

6. 不可变引用(&T

let s = String::from("hello");
let r1 = &s;
let r2 = &s;          // ✅ 允许多个不可变引用共存
println!("{} {} {}", s, r1, r2); // ✅ 全部可用

规则:多个不可变引用可以同时存在,原值也可以被读取。


7. 可变引用(&mut T

let mut s = String::from("hello");
let r1 = &mut s;      // 可变引用
r1.push_str(" world");
// let r2 = &mut s;   // ❌ 同一作用域不能有两个可变引用

规则:同一时刻只能有一个可变引用


8. 不可变引用与可变引用互斥 ⚠️ 1-6 错误点

let mut s = String::from("hello");
let r1 = &s;          // 不可变借用
let r2 = &s;          // 不可变借用
let r3 = &mut s;      // ❌ 已有不可变借用时,不能创建可变借用
println!("{} {} {}", r1, r2, r3);

原答认为能编译 → 不能。 不可变和可变引用不能共存于重叠的作用域中。


9. NLLNon-Lexical Lifetimes—— 3-3 的修复原理

Rust 2018+ 引入 NLL引用的生命周期结束于它最后一次被使用的语句,而非作用域末尾。

let mut s = String::from("hello");

let r1 = &mut s;
r1.push_str(", world");
println!("{}", r1);   // ← r1 最后一次使用NLL 在此结束其借用

let r2 = &mut s;      // ✅ r1 已失效,可以创建新的可变引用
r2.push_str("!");
println!("{}", r2);

同一段代码在 let r1 = &mut s;let r2 = &mut s; 之间如果没有 NLL按词法作用域会冲突。NLL 使借用更短、更精确。

但 NLL 不破坏规则:如果 r1 在 r2 创建后仍然被使用(比如 println 在最后),编译仍然失败。


10. 悬垂引用Dangling Reference—— 3-2 真正原因

fn dangle() -> &String {  // ❌ 返回引用
    let s = String::from("hello");
    &s                      // s 在函数结束时被 drop引用悬垂
}

原答写"返回值被移动了" → 不准确。 真正原因返回的引用指向一个即将被释放的局部变量。Rust 编译期拒绝这种代码。

修复:

fn dangle() -> String {   // ✅ 返回所有权
    let s = String::from("hello");
    s
}

11. 切片与借用冲突 —— 3-4

let s = String::from("hello world");
let first = &s[0..5];   // first 是切片引用(不可变借用)
s.clear();               // ❌ clear() 需要 &mut self与 first 冲突
println!("{}", first);

核心冲突不可变引用切片和可变引用clear不能共存。 修复方法:

let s = String::from("hello world");
let first = &s[0..5];
println!("first = {}", first);  // 先使用 first
let mut s = s;                  // 重新绑定first 的借用已结束
s.clear();

12. str vs String —— 4-4 错误点

类型 说明
String 堆上分配、可增长的字符串。有所有权。Sized
&str 字符串切片引用。借用。Sized(引用本身是指针+长度)。
str 动态大小类型DST,编译期大小未知。不能直接作为参数/变量类型。
fn consume_and_print(s: str) { }       // ❌ str 是 DST
fn consume_and_print(s: String) { }    // ✅ 拿走所有权
fn consume_and_print(s: &str) { }      // ✅ 借用(推荐用于只读场景)

fn append_exclamation(s: &mut str) { } // ❌ str 是 DST
fn append_exclamation(s: &mut String) {} // ✅ 可变引用

13. &str 是借用,不会转移所有权 —— 5-3 核心纠正

let x = String::from("hello");
let y = &x;    // 不可变引用x 仍然拥有 String
let z = &x;    // 另一个不可变引用
println!("{} {} {}", x, y, z); // ✅ 全部合法

原答说"所有权被转移给 y 和 z" → 彻底错误。

所有权转移只有两种方式:

  1. 赋值没有 &let y = x;
  2. 传参没有 &fn foo(s: String) + foo(x);

& 就是借用,不是转移。


14. 借用后修改的影响 —— 5-2 输出纠正

let mut s = String::from("rust");
let r3 = &mut s;
r3.push_str(" is great");  // ⚠️ 通过可变引用修改了 s 的内容
println!("{}", r3);        // "rust is great"
// r3 不再使用

let s2 = s;                // s 现在是 "rust is great",移动给 s2
println!("{}", s2);        // "rust is great"

原答认为最后输出是 "rust" → 忽视了 r3 对 s 的修改。


15. 常见错误速查

错误类型 示例 编译器信息
use after move let y = x; println!("{}", x); E0382: use of moved value
两个可变引用 let r1 = &mut s; let r2 = &mut s; E0499: cannot borrow as mutable more than once
不可变+可变冲突 let r = &s; let rm = &mut s; E0502: cannot borrow as mutable, immutable borrow exists
悬垂引用 fn f() -> &String { &s } E0106: missing lifetime specifier / E0515
返回局部引用 fn f() -> &String { &s } where s is local 生命周期错误
DST 作参数 fn f(s: str) { } E0277: the size is not known at compile time

16. 决策总结

需求 签名
只读取,原值还要用 fn f(s: &str)fn f(s: &String)
只读取,可能存下来 fn f(s: &str) -> String 返回新值
修改原值 fn f(s: &mut String)
拿走所有权 fn f(s: String)
函数只读 优先用 &str(比 &String 更通用)