"补充第二章所有权内容,新增移动语义、借用检查、字符串转换等详细示例和实践"

This commit is contained in:
程广 2025-07-02 18:20:40 +08:00
parent 52a1aa7bdf
commit 0b1fc76e73
1 changed files with 221 additions and 1 deletions

222
02.md
View File

@ -1,4 +1,92 @@
# 第二章所有权Rust的灵魂解码补充版
# 第二章所有权Rust的灵魂解码
所有权系统是Rust最独特且强大的特性它让Rust能够在编译期保证内存安全而无需垃圾回收。本章将深入探讨所有权的核心机制及其工程实践。
## 2.1 移动语义 vs 克隆
Rust中的赋值操作默认采用移动语义而非浅拷贝或深拷贝。理解这一点对掌握Rust至关重要。
### 移动语义示例
```rust
fn main() {
let s1 = String::from("Rust");
let s2 = s1; // s1的所有权移动到s2
// println!("{}", s1); // 编译错误s1不再有效
println!("{}", s2); // 正确
}
```
### 克隆实现深拷贝
```rust
fn main() {
let s1 = String::from("Rust");
let s2 = s1.clone(); // 显式深拷贝
println!("s1 = {}, s2 = {}", s1, s2); // 两者都有效
}
```
### 栈数据的拷贝语义
```rust
fn main() {
let x = 5;
let y = x; // 栈上的简单值自动拷贝
println!("x = {}, y = {}", x, y); // 两者都有效
}
```
## 2.2 借用检查器错误分析
Rust的借用检查器防止数据竞争和悬垂指针。以下是常见错误及解决方案
### 错误1同时存在可变与不可变引用
```rust
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 不可变借用
let r2 = &s; // 另一个不可变借用
let r3 = &mut s; // 错误!可变借用冲突
println!("{}, {}, and {}", r1, r2, r3);
}
```
**解决方案**:确保作用域不重叠
```rust
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2); // 作用域结束
let r3 = &mut s; // 现在允许
r3.push_str(", world");
}
```
### 错误2悬垂引用
```rust
fn main() {
let r = dangle(); // 返回悬垂引用
}
fn dangle() -> &String {
let s = String::from("hello");
&s // 错误s离开作用域被释放
}
```
**解决方案**:返回所有权而非引用
```rust
fn no_dangle() -> String {
let s = String::from("hello");
s // 所有权被移出
}
```
## 2.3 生命周期标注详解:实践与约束
@ -116,6 +204,138 @@ fn main() {
}
```
## 2.4 String与&str转换场景
理解String(堆分配)和&str(字符串切片)的区别及转换:
| 操作 | 方法 | 说明 |
|------|------|------|
| String → &str | `&s``s.as_str()` | 零成本转换 |
| &str → String | `to_string()``String::from()` | 需要内存分配 |
| 连接String | `s1 + &s2` | s2需转换为&str |
| 连接多个 | `format!("{}{}", s1, s2)` | 更清晰的方式 |
```rust
fn process_text(text: &str) { // 接受String或&str
println!("Processing: {}", text);
}
fn main() {
let s = String::from("hello");
let slice = "world";
process_text(&s); // String转&str
process_text(slice); // 直接使用&str
let combined = s + " " + slice; // 连接字符串
println!("{}", combined);
}
```
## 2.5 实现其他语言的常量与变量
Rust通过`let`和`const`提供变量和常量,但语义与其他语言不同:
| 类型 | 关键字 | 可变性 | 作用域 | 内存位置 |
|------|--------|--------|--------|----------|
| 变量 | `let` | 默认不可变,`mut`可变 | 块作用域 | 栈/堆 |
| 常量 | `const` | 永远不可变 | 全局 | 编译期已知 |
| 静态变量 | `static` | 可声明为可变(unsafe) | 全局 | 固定内存地址 |
```rust
const MAX_USERS: u32 = 100_000; // 编译时常量
static mut COUNTER: u32 = 0; // 可变全局变量(需unsafe)
fn main() {
// 不可变变量
let x = 5;
// x = 6; // 错误
// 可变变量
let mut y = 10;
y += 1;
// 隐藏变量
let z = 5;
let z = z + 1;
let z = z * 2;
println!("z = {}", z); // 12
// 使用全局可变变量(需要unsafe块)
unsafe {
COUNTER += 1;
println!("Counter: {}", COUNTER);
}
}
```
## 2.6 实例:安全字符串处理器
实现一个安全的字符串处理器,避免悬垂指针:
```rust
struct StringProcessor<'a> {
source: &'a str,
processed: String,
}
impl<'a> StringProcessor<'a> {
fn new(source: &'a str) -> Self {
StringProcessor {
source,
processed: String::new(),
}
}
fn process(&mut self) {
// 示例处理:转换为大写并添加前缀
self.processed = self.source
.chars()
.map(|c| c.to_ascii_uppercase())
.collect();
self.processed = format!("PROCESSED: {}", self.processed);
}
fn get_result(&self) -> &str {
&self.processed
}
}
fn main() {
let input = String::from("hello rust");
let mut processor = StringProcessor::new(&input);
processor.process();
// input在这里仍然有效
println!("Original: {}", input);
println!("Result: {}", processor.get_result());
// 处理器结果的生命周期独立于输入
let result;
{
let temp = String::from("temporary");
let mut p2 = StringProcessor::new(&temp);
p2.process();
result = p2.get_result().to_string(); // 获取所有权
} // temp离开作用域被释放
// 但result拥有独立的数据
println!("Saved result: {}", result);
}
```
### 安全设计要点:
1. 使用生命周期标注确保源字符串的引用有效
2. 处理结果存储在String中拥有独立所有权
3. 返回结果时提供引用,避免不必要的拷贝
4. 需要长期保存结果时使用to_string()获取所有权
## 2.7 实例:单例模式的安全实现
在Rust中实现线程安全的单例模式需要特殊处理因为全局可变状态需要同步机制。以下是使用`OnceLock`的现代实现: