diff --git a/02.md b/02.md index dc60144..29485ca 100644 --- a/02.md +++ b/02.md @@ -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`的现代实现: