From 73e25a8e128ecf0f70759c9f10b9d1e9431632b4 Mon Sep 17 00:00:00 2001 From: Yuhang Wu Date: Wed, 8 Jul 2026 11:34:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(part3):=20=E6=B7=BB=E5=8A=A0Rust=E6=89=80?= =?UTF-8?q?=E6=9C=89=E6=9D=83=E6=A6=82=E5=BF=B5=E8=AF=A6=E8=A7=A3=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=92=8C=E7=BB=83=E4=B9=A0=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加了完整的Rust所有权系统学习材料,包括: - 详细的所有权概念解释文档,涵盖移动、借用、切片等核心概念 - 包含基础题、填空题、编程题等多种类型的练习题 - 创建Cargo项目框架和示例代码 - 涵盖所有权规则、引用与借用、可变引用等关键知识点 --- part3/Cargo.toml | 6 + part3/src/main.rs | 3 + part3/所有权概念.md | 440 +++++++++++++++++++++++++++++++++ part3/所有权练习题.md | 555 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1004 insertions(+) create mode 100644 part3/Cargo.toml create mode 100644 part3/src/main.rs create mode 100644 part3/所有权概念.md create mode 100644 part3/所有权练习题.md diff --git a/part3/Cargo.toml b/part3/Cargo.toml new file mode 100644 index 0000000..cf9464c --- /dev/null +++ b/part3/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "part3" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/part3/src/main.rs b/part3/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/part3/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/part3/所有权概念.md b/part3/所有权概念.md new file mode 100644 index 0000000..ce78fc9 --- /dev/null +++ b/part3/所有权概念.md @@ -0,0 +1,440 @@ +# Rust 所有权(Ownership)概念详解 + +> 所有权是 Rust 最独特的特性,它让 Rust 无需垃圾回收器就能保证内存安全。 + +## 目录 + +- [1. 什么是所有权](#1-什么是所有权) +- [2. 所有权规则](#2-所有权规则) +- [3. 变量作用域](#3-变量作用域) +- [4. String 类型与内存分配](#4-string-类型与内存分配) +- [5. 移动(Move)](#5-移动move) +- [6. 克隆(Clone)](#6-克隆clone) +- [7. Copy 特征](#7-copy-特征) +- [8. 所有权与函数](#8-所有权与函数) +- [9. 返回值与作用域](#9-返回值与作用域) +- [10. 引用与借用](#10-引用与借用) +- [11. 可变引用](#11-可变引用) +- [12. 悬垂引用](#12-悬垂引用) +- [13. 切片类型](#13-切片类型) +- [14. 总结](#14-总结) + +--- + +## 1. 什么是所有权 + +所有权是一套用于管理内存的规则。在大部分编程语言中,内存管理通过以下方式处理: + +- **手动管理**(如 C/C++):程序员显式分配和释放内存 +- **垃圾回收**(如 Java/Go):运行时自动回收不再使用的内存 +- **所有权系统**(Rust):通过编译时检查的一套规则管理内存 + +### 为什么需要所有权? + +- 无需垃圾回收,性能更高 +- 无需手动管理内存,更安全 +- 在编译期消除内存错误 + +--- + +## 2. 所有权规则 + +三条核心规则: + +1. Rust 中的**每一个值**都有一个**所有者**(owner) +2. 值在任一时刻**有且仅有一个**所有者 +3. 当所有者离开作用域,值会被**自动释放**(调用 `drop`) + +--- + +## 3. 变量作用域 + +```rust +fn main() { + // s 在这里无效,尚未声明 + let s = "hello"; // s 从此处开始有效 + + // 可以使用 s + println!("{}", s); +} // 此作用域结束,s 不再有效 +``` + +--- + +## 4. String 类型与内存分配 + +字符串字面量 `"hello"` 是不可变的,编译时已知,存储在二进制文件中。对于在运行时可变的字符串,使用 `String` 类型。 + +```rust +fn main() { + let mut s = String::from("hello"); + + s.push_str(", world!"); // push_str() 追加字符串 + + println!("{}", s); // 输出: hello, world! +} +``` + +### 内存分配与释放 + +`String` 类型的数据存储在**堆**上: + +- 栈:存储指针、长度、容量(固定大小,编译时已知) +- 堆:存储实际的字符串内容(编译时大小未知,运行时动态分配) + +```text + 栈 堆 + ┌──────────┐ ┌──────────┐ + │ ptr │────────>│ h e l l o│ + │ len: 5 │ │ w o r l d│ + │ cap: 5 │ └──────────┘ + └──────────┘ +``` + +当变量离开作用域时,Rust 自动调用 `drop` 函数,将堆内存归还给操作系统。 + +--- + +## 5. 移动(Move) + +```rust +let x = 5; +let y = x; // x 的值被复制到 y(栈上数据,Copy 类型) +println!("{}", x); // ✅ 正常工作,x 仍可用 + +let s1 = String::from("hello"); +let s2 = s1; // s1 的所有权**移动**到了 s2 +// println!("{}", s1); // ❌ 编译错误!s1 已失效 +println!("{}", s2); // ✅ 正常工作 +``` + +### 为什么需要移动? + +如果 Rust 对堆数据也做浅拷贝,当 `s1` 和 `s2` 同时离开作用域时,它们都会尝试释放同一块堆内存,导致**二次释放**错误。 + +Rust 通过**移动**解决了这个问题:赋值后旧变量失效,只有一个所有者负责释放内存。 + +```text + 移动前: 移动后: + s1 → [堆内存] s2 → [堆内存] + s1 已失效 ❌ +``` + +--- + +## 6. 克隆(Clone) + +如果确实需要深度复制堆数据,使用 `clone` 方法: + +```rust +let s1 = String::from("hello"); +let s2 = s1.clone(); // 在堆上创建一份完整副本 + +println!("s1 = {}, s2 = {}", s1, s2); // ✅ 两者都可用 +``` + +```text + 克隆后: + s1 → [堆内存1: "hello"] + s2 → [堆内存2: "hello"] +``` + +- `clone` 会执行**深拷贝**,包括堆数据 +- 代价较高,仅在确实需要时使用 + +--- + +## 7. Copy 特征 + +对于**完全存储在栈上**的数据类型,Rust 提供了 `Copy` 特征: + +```rust +let x = 5; +let y = x; // x 被复制,而非移动 +println!("{}", x); // ✅ 仍可使用 +``` + +### 具有 Copy 的类型 + +- 所有整数类型:`u32`、`i64` 等 +- 所有浮点类型:`f32`、`f64` +- 布尔类型:`bool` +- 字符类型:`char` +- 元组(仅当所有元素都实现了 Copy):`(i32, bool)` ✅,`(i32, String)` ❌ + +### 不具有 Copy 的类型 + +- `String` +- `Vec` +- 任何实现了 `Drop` trait 的类型 + +--- + +## 8. 所有权与函数 + +将值传递给函数时,会发生**移动**或**复制**,规则与赋值相同: + +```rust +fn main() { + let s = String::from("hello"); + + takes_ownership(s); // s 的所有权移动到函数内 + // println!("{}", s); // ❌ 编译错误!s 已失效 + + let x = 5; + + makes_copy(x); // x 被复制到函数内(i32 是 Copy 类型) + println!("{}", x); // ✅ x 仍然可用 +} // x 离开作用域,然后 s 离开作用域(但 s 已被移动,不会发生什么) + +fn takes_ownership(some_string: String) { + println!("{}", some_string); +} // some_string 离开作用域,drop 被调用,内存释放 + +fn makes_copy(some_integer: i32) { + println!("{}", some_integer); +} // some_integer 离开作用域,无特殊操作(栈数据) +``` + +--- + +## 9. 返回值与作用域 + +函数的返回值也可以**转移所有权**: + +```rust +fn main() { + let s1 = gives_ownership(); // 返回值将所有权移给 s1 + + let s2 = String::from("hello"); + + let s3 = takes_and_gives_back(s2); // s2 移入函数,函数返回后又移给 s3 + // println!("{}", s2); // ❌ s2 已失效 + + println!("s1 = {}, s3 = {}", s1, s3); +} // s1、s3 离开作用域并被释放;s2 已被移动,不触发释放 + +fn gives_ownership() -> String { + let some_string = String::from("hello"); + some_string // 返回 some_string,所有权移出 +} + +fn takes_and_gives_back(a_string: String) -> String { + a_string // 返回 a_string,所有权移出 +} +``` + +### 元组返回多个值 + +```rust +fn main() { + let s1 = String::from("hello"); + let (s2, len) = calculate_length(s1); + println!("'{}' 的长度是 {}", s2, len); +} + +fn calculate_length(s: String) -> (String, usize) { + let length = s.len(); + (s, length) // 返回字符串和它的长度 +} +``` + +这种写法有些繁琐,Rust 提供了**引用**来简化。 + +--- + +## 10. 引用与借用 + +**引用**允许在不获取所有权的情况下使用值。 + +```rust +fn main() { + let s1 = String::from("hello"); + + let len = calculate_length(&s1); // 传递引用,不转移所有权 + + println!("'{}' 的长度是 {}", s1, len); // ✅ s1 仍可使用 +} + +fn calculate_length(s: &String) -> usize { // s 是对 String 的引用 + s.len() +} // s 离开作用域,但由于它不拥有值的所有权,不会调用 drop +``` + +```text + s1 → [堆内存: "hello"] + ↑ + &s1(引用,指向 s1 的值) +``` + +### 引用的规则 + +1. 引用**不获取**所有权 +2. 创建引用的行为称为**借用**(borrowing) +3. 借用者离开作用域时,不会释放底层数据 + +--- + +## 11. 可变引用 + +默认情况下,引用是**不可变的**。要修改借用的值,需要使用**可变引用**: + +```rust +fn main() { + let mut s = String::from("hello"); + + change(&mut s); // 传递可变引用 + + println!("{}", s); // 输出:hello, world! +} + +fn change(some_string: &mut String) { + some_string.push_str(", world!"); +} +``` + +### 可变引用的限制 + +**在任意给定时间,只能拥有以下两者之一:** + +- 一个可变引用 +- 任意数量的不可变引用 + +```rust +let mut s = String::from("hello"); + +// ✅ 不可变引用可以同时存在多个 +let r1 = &s; +let r2 = &s; +println!("{} and {}", r1, r2); + +// ✅ 不可变引用不再使用后,可以创建可变引用 +let r3 = &mut s; +println!("{}", r3); +``` + +```rust +let mut s = String::from("hello"); + +let r1 = &s; // 不可变借用 +let r2 = &s; // 不可变借用 +let r3 = &mut s; // ❌ 编译错误!不能同时存在不可变和可变引用 + +println!("{}, {}, {}", r1, r2, r3); +``` + +```rust +let mut s = String::from("hello"); + +let r1 = &mut s; +let r2 = &mut s; // ❌ 编译错误!不能同时存在两个可变引用 + +println!("{}, {}", r1, r2); +``` + +**这样设计的好处:在编译时防止数据竞争!** + +> 数据竞争发生的三个条件: +> 1. 两个或多个指针同时访问同一数据 +> 2. 至少有一个指针用于写入 +> 3. 没有同步数据访问的机制 + +Rust 的规则直接在编译期消灭了数据竞争。 + +--- + +## 12. 悬垂引用 + +**悬垂引用**是指向已被释放内存的指针。Rust 编译器保证永远不会产生悬垂引用: + +```rust +fn main() { + let reference_to_nothing = dangle(); // ❌ 编译错误! +} + +fn dangle() -> &String { + let s = String::from("hello"); + &s // 返回 s 的引用 +} // s 离开作用域,内存被释放;但返回的引用仍指向已释放的内存 +``` + +编译器错误信息: +``` +error[E0106]: missing lifetime specifier + | + | fn dangle() -> &String { + | ^ expected named lifetime parameter +``` + +正确的做法是直接返回 `String`,将所有权移出去: + +```rust +fn no_dangle() -> String { + let s = String::from("hello"); + s // 所有权移出 +} +``` + +--- + +## 13. 切片类型 + +**切片**是一种不获取所有权的引用,允许引用集合中一段连续的元素。 + +### 字符串切片 + +```rust +let s = String::from("hello world"); + +let hello = &s[0..5]; // "hello" +let world = &s[6..11]; // "world" +``` + +语法简写: + +```rust +let s = String::from("hello world"); + +let hello = &s[..5]; // 从 0 开始 +let world = &s[6..]; // 到末尾 +let whole = &s[..]; // 整个字符串 +``` + +### 字符串字面量就是切片 + +```rust +let s = "Hello, world!"; // s 的类型是 &str,即字符串切片 +``` + +### 其他切片 + +```rust +let a = [1, 2, 3, 4, 5]; + +let slice = &a[1..3]; // 类型是 &[i32],包含 [2, 3] + +assert_eq!(slice, &[2, 3]); +``` + +--- + +## 14. 总结 + +| 概念 | 说明 | +|------|------| +| **所有权** | 每个值有且仅有一个所有者;所有者离开作用域时值被释放 | +| **移动** | 赋值/传参时转移所有权,原变量失效 | +| **克隆** | 深拷贝堆数据,两个变量独立 | +| **Copy** | 栈上数据的自动复制(整数、布尔等基础类型) | +| **引用** | 不获取所有权的指针,允许多个存在 | +| **可变引用** | 可修改数据的引用,同时只能有一个 | +| **切片** | 对集合局部元素的引用 | +| **借用规则** | 任意时刻:要么一个可变引用,要么多个不可变引用 | + +### 借用检查器保证了 + +- **内存安全**:无悬垂指针、无二次释放、无使用后释放 +- **线程安全**:无数据竞争 +- **零运行时开销**:所有检查在编译时完成 + +> 所有权系统可能会让初学者感到"Rust 编译器在跟你作对",但请记住:**编译器不是在刁难你,而是在保护你写出的每一行代码都是内存安全的。** 当你熟悉所有权之后,这些规则会变得自然且合理。 diff --git a/part3/所有权练习题.md b/part3/所有权练习题.md new file mode 100644 index 0000000..94a39f4 --- /dev/null +++ b/part3/所有权练习题.md @@ -0,0 +1,555 @@ +# Rust 所有权练习题 + +> 建议先手动写出每道题的答案,再运行代码验证。 + +## 一、基础题:判断正误 + +判断以下代码能否通过编译。如果可以,说明原因;如果不能,指出错误原因。 + +### 题目 1-1 + +```rust +fn main() { + let s = String::from("hello"); + let t = s; + println!("{}", s); +} +``` + +### 题目 1-2 + +```rust +fn main() { + let x = 42; + let y = x; + println!("x = {}, y = {}", x, y); +} +``` + +### 题目 1-3 + +```rust +fn main() { + let s = String::from("rust"); + let t = s.clone(); + println!("s = {}, t = {}", s, t); +} +``` + +### 题目 1-4 + +```rust +fn main() { + let s1 = String::from("hello"); + let s2 = &s1; + let s3 = &s1; + println!("{} {} {}", s1, s2, s3); +} +``` + +### 题目 1-5 + +```rust +fn main() { + let mut s = String::from("hello"); + let r1 = &mut s; + let r2 = &mut s; + println!("{}, {}", r1, r2); +} +``` + +### 题目 1-6 + +```rust +fn main() { + let mut s = String::from("hello"); + let r1 = &s; + let r2 = &s; + let r3 = &mut s; + println!("{}, {}, {}", r1, r2, r3); +} +``` + +--- + +## 二、填空题:补充代码 + +补全下列代码使其能通过编译。 + +### 题目 2-1:转移所有权 + +```rust +fn main() { + let s = String::from("rustacean"); + takes_ownership(________); // 填空,使 s 的所有权进入函数 + // println!("{}", s); // 此行若取消注释会报错 +} + +fn takes_ownership(s: String) { + println!("{}", s); +} +``` + +### 题目 2-2:借用的生命周期 + +```rust +fn main() { + let s = String::from("hello"); + let len = get_length(________); // 填空:传递引用取得长度 + println!("'{}' 的长度是 {}", s, len); +} + +fn get_length(s: &String) -> usize { + s.len() +} +``` + +### 题目 2-3:可变引用 + +```rust +fn main() { + let ________ s = String::from("hello"); // 填空:声明可变变量 + append_world(________); // 填空:传递可变引用 + println!("{}", s); // 期望输出:hello, world! +} + +fn append_world(s: &mut String) { + s.push_str(", world!"); +} +``` + +### 题目 2-4:作用域技巧 + +```rust +fn main() { + let mut s = String::from("hello"); + + { // 进入新作用域 + let r1 = ________; // 填空:创建不可变引用 + println!("{}", r1); + } // r1 离开作用域 + + let r2 = ________; // 填空:现在可以创建可变引用了 + r2.push_str(" world"); + println!("{}", r2); +} +``` + +--- + +## 三、找出并修复错误 + +以下每段代码都有编译错误,请指出错误并写出修正后的代码。 + +### 题目 3-1 + +```rust +fn main() { + let s1 = String::from("hello"); + let s2 = s1; + print_both(s1, s2); +} + +fn print_both(a: String, b: String) { + println!("{} and {}", a, b); +} +``` + +### 题目 3-2 + +```rust +fn main() { + let s = dangle(); + println!("{}", s); +} + +fn dangle() -> &String { + let s = String::from("hello"); + &s +} +``` + +### 题目 3-3 + +```rust +fn main() { + let mut s = String::from("hello"); + let r1 = &mut s; + r1.push_str(", world"); + let r2 = &mut s; + r2.push_str("!"); + println!("{}", r1); + println!("{}", r2); +} +``` + +### 题目 3-4 + +```rust +fn main() { + let s = String::from("hello world"); + let first = &s[0..5]; + s.clear(); // 清空字符串 + println!("first = {}", first); +} +``` + +--- + +## 四、编程题 + +### 题目 4-1:计算单词数量 + +编写一个函数 `count_words(s: &str) -> usize`,计算字符串中的单词数量(以空格分隔)。 + +```rust +fn count_words(s: &str) -> usize { + // 你的代码 +} + +fn main() { + let s = String::from("hello world rust"); + let count = count_words(&s); + println!("'{}' 有 {} 个单词", s, count); // 期望输出:3 + + // 验证 s 的所有权未被取走 + println!("原始字符串仍然可用: {}", s); +} +``` + +### 题目 4-2:获取首尾单词 + +编写函数 `first_and_last(s: &str) -> (&str, &str)`,返回字符串的首单词和尾单词。 + +```rust +fn first_and_last(s: &str) -> (&str, &str) { + // 你的代码 +} + +fn main() { + let s = String::from("Rust is a systems programming language"); + let (first, last) = first_and_last(&s); + println!("首单词: '{}', 尾单词: '{}'", first, last); + // 期望输出:首单词: 'Rust', 尾单词: 'language' + println!("原字符串: {}", s); +} +``` + +### 题目 4-3:字符串处理 + +编写一个函数,接收一个字符串,返回一个新的字符串,将其中的每个单词首字母大写,其余字母小写。 + +```rust +fn title_case(s: &str) -> String { + // 你的代码 + // 提示:可以使用 split_whitespace、to_uppercase、to_lowercase、collect 等方法 +} + +fn main() { + let s = String::from("hello WORLD rust"); + let result = title_case(&s); + println!("输入: {}", s); + println!("输出: {}", result); + // 期望输出:Hello World Rust + + // 验证 s 的所有权未被取走 + println!("原字符串: {}", s); +} +``` + +### 题目 4-4:参数所有权设计 + +请合理设计以下函数的参数类型(传值 / 传引用 / 传可变引用): + +```rust +// fn capitalize(s: ???) // 1. 只读取,不修改,调用后原变量还要用 +// fn consume_and_print(s: ???) // 2. 拿走所有权,调用后原变量不再使用 +// fn append_exclamation(s: ???)// 3. 修改原字符串 + +fn capitalize(s: ________) -> String { + let mut result = s.clone(); + if let Some(c) = result.get_mut(0..1) { + c.make_ascii_uppercase(); + } + result +} + +fn consume_and_print(s: ________) { + println!("消费了: {}", s); +} + +fn append_exclamation(s: ________) { + s.push_str("!"); +} + +fn main() { + let s = String::from("hello"); + + let capitalized = capitalize(________); // 填空:调用 capitalize + println!("{}", s); // s 仍可用 + + consume_and_print(________); // 填空:调用 consume_and_print,传入 s + // println!("{}", s); // 若取消注释会报错 + + let mut t = String::from("hello"); + append_exclamation(________); // 填空:调用 append_exclamation + println!("{}", t); // 期望输出:hello! +} +``` + +### 题目 4-5:数组切片操作 + +编写函数实现以下功能: + +```rust +// 返回数组前 n 个元素的和 +fn sum_of_first_n(arr: &[i32], n: usize) -> i32 { + // 你的代码 + // 提示:使用切片 &arr[..n] +} + +// 判断一个切片是否包含目标值 +fn contains(arr: &[i32], target: i32) -> bool { + // 你的代码 +} + +fn main() { + let numbers = [10, 20, 30, 40, 50]; + + println!("前 3 个元素的和: {}", sum_of_first_n(&numbers, 3)); // 期望:60 + + println!("包含 30 吗? {}", contains(&numbers, 30)); // 期望:true + println!("包含 99 吗? {}", contains(&numbers, 99)); // 期望:false +} +``` + +--- + +## 五、综合思考题 + +### 题目 5-1 + +以下代码为什么不能通过编译?提出两种修复方法,并说明各自的优缺点。 + +```rust +fn main() { + let mut data = vec![1, 2, 3]; + let first = &data[0]; + data.push(4); + println!("first = {}", first); +} +``` + +### 题目 5-2 + +分析以下代码,回答: +1. 写出每一步所有权的状态变化 +2. 最终输出是什么? + +```rust +fn main() { + let mut s = String::from("rust"); + let r1 = &s; + let r2 = &s; + println!("{} and {}", r1, r2); + // r1, r2 此后不再使用 + + let r3 = &mut s; + r3.push_str(" is great"); + println!("{}", r3); + // r3 此后不再使用 + + let s2 = s; // 移动 s + // println!("{}", s); // 若取消注释会怎样? + println!("{}", s2); +} +``` + +### 题目 5-3 + +以下代码在 Rust 中是否合法?为什么? + +```rust +fn main() { + let x = 5; + let y = &x; + let z = &x; + println!("{} {} {}", x, y, z); +} +``` + +如果将 `x` 的类型从 `i32` 改为 `String`,同样的结构是否仍然合法? + +```rust +fn main() { + let x = String::from("hello"); + let y = &x; + let z = &x; + println!("{} {} {}", x, y, z); +} +``` + +### 题目 5-4 + +Rust 的所有权系统与 C++ 的 RAII、Java 的垃圾回收各自有什么优缺点?请简要对比分析。 + +--- + +## 参考答案 + +> 请独立完成再查看答案。 + +
+点击展开答案 + +### 一、基础题 + +**1-1**:❌ 编译错误。`s` 的所有权已移动到 `t`,`s` 已失效。 + +**1-2**:✅ 通过编译。`i32` 实现了 `Copy`,赋值时自动复制。 + +**1-3**:✅ 通过编译。`clone` 创建了深拷贝,两个变量独立。 + +**1-4**:✅ 通过编译。不可变引用可以同时存在多个。 + +**1-5**:❌ 编译错误。同一作用域不能同时存在两个可变引用。 + +**1-6**:❌ 编译错误。已有不可变引用时不能创建可变引用。 + +### 二、填空题 + +**2-1**:`takes_ownership(s);` + +**2-2**:`let len = get_length(&s);` + +**2-3**: +```rust +let mut s = String::from("hello"); +append_world(&mut s); +``` + +**2-4**: +```rust +let r1 = &s; +let r2 = &mut s; +``` + +### 三、修复错误 + +**3-1**:`s1` 已被移动给 `s2`,不能再传给函数。修复: +```rust +fn main() { + let s1 = String::from("hello"); + let s2 = s1.clone(); // 克隆一份 + print_both(s1, s2); +} +``` + +**3-2**:返回了局部变量的引用,生命周期不够。修复: +```rust +fn dangle() -> String { // 返回 String 而非 &String + let s = String::from("hello"); + s // 所有权移出 +} +``` + +**3-3**:`r1` 和 `r2` 的可变借用时间重叠。修复:将两次借用限制在各自的作用域内,或使用 `Non-Lexical Lifetimes` 特性(Rust 2018+ 已默认启用,但此处 `r1` 在 `r2` 创建后仍被使用)。修复方法: +```rust +fn main() { + let mut s = String::from("hello"); + let r1 = &mut s; + r1.push_str(", world"); + println!("{}", r1); + // r1 在 println! 后不再使用 + let r2 = &mut s; + r2.push_str("!"); + println!("{}", r2); +} +``` + +**3-4**:`clear()` 需要一个可变引用,但 `first` 是一个不可变引用,违反了借用规则。修复:先使用 `first`,再 `clear`: +```rust +fn main() { + let s = String::from("hello world"); + let first = &s[0..5]; + println!("first = {}", first); // 先使用 + let mut s = s; // 重新绑定 + s.clear(); +} +``` + +### 四、编程题 + +**4-1**: +```rust +fn count_words(s: &str) -> usize { + s.split_whitespace().count() +} +``` + +**4-2**: +```rust +fn first_and_last(s: &str) -> (&str, &str) { + let words: Vec<&str> = s.split_whitespace().collect(); + (words[0], words[words.len() - 1]) +} +``` + +**4-3**: +```rust +fn title_case(s: &str) -> String { + s.split_whitespace() + .map(|word| { + let mut chars = word.chars(); + match chars.next() { + None => String::new(), + Some(c) => c.to_uppercase().collect::() + &chars.as_str().to_lowercase(), + } + }) + .collect::>() + .join(" ") +} +``` + +**4-4**: +```rust +fn capitalize(s: &str) -> String { /* ... */ } +fn consume_and_print(s: String) { /* ... */ } +fn append_exclamation(s: &mut String) { /* ... */ } + +// 调用方式: +let capitalized = capitalize(&s); // 传引用 +consume_and_print(s); // 传值,所有权移入 +append_exclamation(&mut t); // 传可变引用 +``` + +**4-5**: +```rust +fn sum_of_first_n(arr: &[i32], n: usize) -> i32 { + arr[..n].iter().sum() +} + +fn contains(arr: &[i32], target: i32) -> bool { + arr.contains(&target) +} +``` + +### 五、综合思考题 + +**5-1**:`first` 是对 `data` 元素的不可变引用,`push` 需要一个可变引用,违反借用规则。 +- 方法一:调整顺序,先 push 再取引用 —— 简单但有时逻辑不允许 +- 方法二:使用索引 `let first = data[0]`(需要 Copy 类型)—— 避免了借用但只适用于 Copy 类型 + +**5-2**: +- 步骤:创建 `s` → 两次不可变借用(r1, r2)→ 打印后 r1, r2 释放 → 一次可变借用(r3)→ 打印后 r3 释放 → 移动 `s` 到 `s2` +- 输出:`rust and rust` / `rust is great` / `rust is great` +- 取消注释会报错:`s` 已移动 + +**5-3**:两者都合法。创建引用不会转移 `x` 的所有权,无论 `x` 是什么类型。只要引用和原值不冲突即可(此处都是不可变引用)。 + +**5-4**(简要): +- **C++ RAII**:灵活、性能极高,但依赖程序员自觉,容易出现野指针、重复释放 +- **Java GC**:使用方便,不会出现悬挂指针,但存在运行时开销、不可预测的停顿 +- **Rust 所有权**:编译期零成本抽象,保证内存安全/线程安全,但学习曲线陡峭 + +