知识点:所有权三条核心规则
rust
fn main() {
// 规则1:每个值有且只有一个所有者(owner)
let s1 = String::from("hello");
// s1 是 "hello" 的唯一所有者
// 规则2:所有者离开作用域时,值被丢弃(drop)
{
let s2 = String::from("world");
println!("{}", s2);
} // s2 离开作用域,"world" 被释放
// println!("{}", s2); // ❌ s2 已不存在
// 规则3:值在同一时刻只能有一个所有者
let s3 = String::from("rust");
let s4 = s3; // 所有权从 s3 移动到 s4
// println!("{}", s3); // ❌ s3 已失效(被移动)
println!("{}", s4); // ✅ s4 是新的所有者
}
知识点:移动语义(Move)
rust
fn main() {
// === 堆上数据:赋值 = 移动 ===
let a = String::from("hello");
let b = a; // 所有权移动,a 失效
// println!("{}", a); // ❌ 编译错误:value used here after move
println!("{}", b); // ✅
// === 栈上数据:赋值 = 拷贝(Copy)===
let x = 42;
let y = x; // 拷贝,x 仍然有效
println!("x={}, y={}", x, y); // ✅ 都能用
// 实现了 Copy trait 的类型赋值时是拷贝而非移动:
// - 所有整数类型(i32, u64, ...)
// - 浮点类型(f32, f64)
// - 布尔类型 bool
// - 字符类型 char
// - 元组(当所有元素都实现 Copy 时)
// - 引用 &T
let t1 = (1, 2, 3);
let t2 = t1; // 拷贝(元组元素都是 i32)
println!("t1={:?}, t2={:?}", t1, t2); // ✅ 都能用
let t3 = (1, String::from("hello"), 3);
let t4 = t3; // 移动!(元组包含 String,不实现 Copy)
// println!("{:?}", t3); // ❌ t3 已失效
// === 函数参数也是移动 ===
fn take_ownership(s: String) {
println!("获得了: {}", s);
} // s 离开作用域,被释放
let my_string = String::from("hello");
take_ownership(my_string);
// println!("{}", my_string); // ❌ my_string 已移动进函数
// === 返回值也可以转移所有权 ===
fn give_ownership() -> String {
let s = String::from("给你的");
s // 所有权移动给调用者
}
let received = give_ownership();
println!("收到: {}", received);
// === 移动 vs 拷贝 的本质区别 ===
// 移动:只转移指针(栈上的 ptr/len/capacity),不拷贝堆上数据
// 拷贝:复制栈上的值(因为数据全在栈上)
}
知识点:克隆 --- 深拷贝
rust
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝:堆上数据也复制一份
println!("s1={}, s2={}", s1, s2); // ✅ 都能用
// clone 的开销:
// - String::clone 会分配新堆内存并复制内容
// - Vec::clone 会分配新堆内存并逐个克隆元素
// - HashMap::clone 会分配新堆内存并复制所有键值对
let v1 = vec![1, 2, 3];
let v2 = v1.clone();
println!("v1={:?}, v2={:?}", v1, v2);
// 嵌套结构的 clone
let data = vec![
String::from("a"),
String::from("b"),
];
let data2 = data.clone(); // 深拷贝整个 Vec 及其中的每个 String
println!("{:?}, {:?}", data, data2);
// clone 与 move 的性能对比
let big_string = "x".repeat(1_000_000); // 1MB 字符串
// 移动:O(1),只复制 3 个 usize(ptr, len, cap)
let moved = big_string;
// 克隆:O(n),分配 1MB 新内存并复制
let cloned = moved.clone();
println!("moved 长度: {}", moved.len());
println!("cloned 长度: {}", cloned.len());
}
知识点:引用与借用(Borrowing)
rust
fn main() {
// === 不可变引用 &T(共享引用)===
let s = String::from("hello");
// &s 创建不可变引用(借用)
// 不转移所有权,s 仍然是所有者
let len = calculate_length(&s);
println!("'{}' 的长度是 {}", s, len); // ✅ s 仍可用
// 多个不可变引用可以同时存在
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{}, {}, {}", r1, r2, r3); // ✅
// === 可变引用 &mut T(独占引用)===
let mut s = String::from("hello");
change(&mut s);
println!("修改后: {}", s); // hello, world
// === 借用规则(编译期强制执行)===
// 规则A:同一时刻,要么只有一个可变引用,要么有多个不可变引用
// 规则B:引用必须始终有效(不能指向已释放的数据)
// 违反规则A的例子:
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2);
// r1 和 r2 在这之后不再使用(NLL:非词法生命周期)
let r3 = &mut s; // ✅ 因为 r1、r2 的最后使用已经结束
println!("{}", r3);
// === 引用也是值(遵循移动规则)===
let s = String::from("hello");
let r = &s; // r 是 &String 类型
let r2 = r; // 引用实现了 Copy,所以是拷贝
println!("{} {}", r, r2); // ✅ 都能用
// 但可变引用不实现 Copy
let mut s = String::from("hello");
let r = &mut s;
let r2 = r; // 移动!r 失效
// println!("{}", r); // ❌ r 已移动
println!("{}", r2); // ✅
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s 是引用,离开作用域时不会释放数据
fn change(s: &mut String) {
s.push_str(", world");
}
知识点:悬垂引用(Dangling Reference)
rust
fn main() {
// Rust 编译器会阻止悬垂引用
// ❌ 这段代码无法编译:
// fn dangle() -> &String {
// let s = String::from("hello");
// &s // s 离开作用域后被释放,引用变成悬垂
// }
// ✅ 正确做法:返回所有权
fn no_dangle() -> String {
let s = String::from("hello");
s // 移动所有权给调用者
}
let s = no_dangle();
println!("{}", s);
// 另一种悬垂引用场景:
let reference = {
let x = 42;
// &x // ❌ x 即将被释放
// 正确做法:
x // 返回值的拷贝(i32 实现了 Copy)
};
println!("{}", reference);
// 条件分支中的生命周期问题
let s;
{
let inner = String::from("inner");
// s = &inner; // ❌ inner 在块结束时被释放
s = inner; // ✅ 移动所有权
}
println!("{}", s); // ✅ s 拥有数据
}
知识点:切片(Slice)--- 不拥有数据的引用
fn main() {
// === 字符串切片 &str ===
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
let all = &s[..]; // "hello world"(整个字符串)
let hello2 = &s[..5]; // "hello"(从开头)
let world2 = &s[6..]; // "world"(到结尾)
println!("{}, {}, {}", hello, world, all);
println!("{}, {}", hello2, world2);
// 字符串切片的类型是 &str
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[..i];
}
}
s
}
let word = first_word("hello world");
println!("第一个词: {}", word); // hello
// 字面量本身就是 &str
let literal: &str = "这是字面量"; // 存储在二进制文件中,生命周期为 'static
// === 数组切片 &[T] ===
let arr = [10, 20, 30, 40, 50];
let slice1 = &arr[1..3]; // &[20, 30]
let slice2 = &arr[..2]; // &[10, 20]
let slice3 = &arr[3..]; // &[40, 50]
let all = &arr[..]; // &[10, 20, 30, 40, 50]
println!("{:?}, {:?}, {:?}", slice1, slice2, slice3);
// 切片作为函数参数(更通用)
fn sum(slice: &[i32]) -> i32 {
slice.iter().sum()
}
let vec = vec![1, 2, 3, 4, 5];
let arr = [10, 20, 30];
println!("Vec 切片之和: {}", sum(&vec)); // 15
println!("数组切片之和: {}", sum(&arr)); // 60
println!("部分切片之和: {}", sum(&vec[1..4])); // 9
// === 切片的本质 ===
// 切片是一个胖指针(fat pointer):
// - 指向数据起始位置的指针
// - 切片的长度
// 大小 = 2 个 usize(16 字节 on 64位)
// === 可变切片 ===
let mut arr = [1, 2, 3, 4, 5];
let slice = &mut arr[1..4];
slice[0] = 20;
slice[1] = 30;
slice[2] = 40;
println!("修改后: {:?}", arr); // [1, 20, 30, 40, 5]
}
知识点:所有权与结构体
rust
// 结构体可以拥有数据
struct Owner {
name: String, // 拥有 String 数据
items: Vec<String>, // 拥有 Vec 数据
}
// 结构体也可以持有引用(需要生命周期标注)
struct Borrower<'a> {
name: &'a str, // 借用字符串切片
items: &'a [String], // 借用切片
}
fn struct_ownership() {
// 结构体拥有数据
let owner = Owner {
name: String::from("Alice"),
items: vec![String::from("书"), String::from("笔")],
};
println!("{} 拥有 {:?}", owner.name, owner.items);
// 移动结构体中的字段
let name = owner.name; // name 被移动出来
// println!("{}", owner.name); // ❌ 部分移动后不能访问整个结构体
println!("名字: {}", name);
// 但其他字段仍可使用(如果没被移动)
// 注意:实际上部分移动后整个结构体都不能用了
// 需要用 Option 或 RefCell 来解决
// 结构体借用数据
let data = vec![String::from("x"), String::from("y")];
let borrower = Borrower {
name: "Bob",
items: &data,
};
println!("{} 借用 {:?}", borrower.name, borrower.items);
}
// === 部分移动问题与解决方案 ===
struct Config {
host: String,
port: u16,
debug: bool,
}
fn partial_move_solution() {
let config = Config {
host: String::from("localhost"),
port: 8080,
debug: true,
};
// 方案1:用引用避免移动
let host_ref = &config.host;
println!("host={}, port={}, debug={}", host_ref, config.port, config.debug);
// ✅ config 完整可用
// 方案2:实现 Clone
let host_clone = config.host.clone();
println!("克隆的host: {}", host_clone);
println!("原config仍可用: {}", config.host);
// 方案3:用 Copy 类型(port 和 debug 是 Copy 的)
let port = config.port; // 拷贝
let debug = config.debug; // 拷贝
println!("port={}, debug={}", port, debug);
println!("config.port={}, config.debug={}", config.port, config.debug);
// ✅ Copy 类型不会导致部分移动
}
fn main() {
struct_ownership();
println!("---");
partial_move_solution();
}
知识点:函数签名与所有权传递
rust
// 三种函数参数模式:
// 模式1:获取所有权
fn takes_ownership(s: String) {
println!("我拥有了: {}", s);
} // s 在这里被释放
// 模式2:不可变借用(只读)
fn borrows_immutable(s: &String) {
println!("我只读: {}", s);
} // 不释放,借用结束
// 模式3:可变借用(读写)
fn borrows_mutable(s: &mut String) {
s.push_str(" (已修改)");
println!("我修改了: {}", s);
} // 不释放,借用结束
// 更好的做法:用 &str 代替 &String
fn better_borrow(s: &str) {
println!("通用借用: {}", s);
}
fn main() {
// 模式1
let s1 = String::from("hello");
takes_ownership(s1);
// println!("{}", s1); // ❌ s1 已移动
// 模式2
let s2 = String::from("world");
borrows_immutable(&s2);
println!("s2 仍可用: {}", s2); // ✅
// 模式3
let mut s3 = String::from("rust");
borrows_mutable(&mut s3);
println!("s3 仍可用: {}", s3); // ✅
// 更好的做法
better_borrow(&s2); // &String 可以自动转为 &str
better_borrow("字面量"); // &str 直接传入
better_borrow(&s3);
// === 返回值转移所有权 ===
fn give_back(s: String) -> String {
println!("临时拥有: {}", s);
s // 归还所有权
}
let s4 = String::from("temp");
let s4 = give_back(s4); // 移动进去,再移动出来
println!("拿回来了: {}", s4);
// === 同时借用和返回引用 ===
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
let result = longest("hello", "hi");
println!("更长的: {}", result);
}
知识点:生命周期标注基础
rust
// 生命周期标注不改变引用的实际存活时间
// 它只是告诉编译器多个引用之间的关系
// 函数返回引用时,必须标注生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 'a 的含义:返回值的生命周期不会超过 x 和 y 中较短的那个
fn lifetime_examples() {
let s1 = String::from("long string");
let result;
{
let s2 = String::from("xyz");
result = longest(s1.as_str(), s2.as_str());
println!("结果: {}", result); // ✅ result 在 s2 的作用域内使用
}
// println!("结果: {}", result); // ❌ result 可能引用已释放的 s2
// 静态生命周期
let s: &'static str = "我活在整个程序期间";
println!("{}", s);
// 结构体中的生命周期
#[derive(Debug)]
struct Important<'a> {
text: &'a str,
}
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = &novel[..];
let excerpt = Important { text: first_sentence };
println!("{:?}", excerpt);
}
// 省略规则(Lifetime Elision)
// 编译器自动推断生命周期的三条规则:
// 1. 每个引用参数获得自己的生命周期参数
// 2. 如果只有一个输入生命周期,它被赋给所有输出引用
// 3. 如果有 &self 或 &mut self,self 的生命周期被赋给所有输出引用
fn elision_examples() {
// 编译器自动推断:
// fn first_word(s: &str) -> &str
// 等价于:
// fn first_word<'a>(s: &'a str) -> &'a str
// fn longest(x: &str, y: &str) -> &str // ❌ 无法推断(两个输入生命周期)
// 必须手动标注:
// fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
// 方法签名自动推断:
// impl MyStruct { fn get(&self) -> &str }
// 等价于:
// fn get<'a>(&'a self) -> &'a str
}
fn main() {
lifetime_examples();
elision_examples();
}
知识点:常见所有权陷阱与最佳实践
rust
fn main() {
// === 陷阱1:循环中移动 ===
let names = vec![
String::from("Alice"),
String::from("Bob"),
];
// ❌ for name in names { ... } // names 被移动进循环
// ✅ 用引用遍历
for name in &names {
println!("{}", name);
}
println!("names 仍可用: {:?}", names); // ✅
// === 陷阱2:闭包捕获所有权 ===
let greeting = String::from("Hello");
// move 关键字强制闭包获取所有权
let closure = move || {
println!("{}, World!", greeting);
};
// println!("{}", greeting); // ❌ greeting 已移动进闭包
closure();
// 不移动:用引用捕获
let name = String::from("Rust");
let closure = || {
println!("Hello, {}!", name); // 借用 name
};
closure();
println!("name 仍可用: {}", name); // ✅
// === 陷阱3:在循环中累积 String ===
let words = vec!["hello", "world", "rust"];
// ❌ 低效:每次循环都创建新 String
// let mut result = String::new();
// for w in &words {
// result = result + w + " "; // 每次都重新分配
// }
// ✅ 高效:预分配容量
let mut result = String::with_capacity(
words.iter().map(|w| w.len() + 1).sum::<usize>()
);
for w in &words {
result.push_str(w);
result.push(' ');
}
println!("结果: '{}'", result.trim());
// === 陷阱4:返回局部变量的引用 ===
// fn bad() -> &String {
// let s = String::from("hello");
// &s // ❌ 悬垂引用
// }
// ✅ 返回所有权
fn good() -> String {
let s = String::from("hello");
s
}
// === 最佳实践 ===
// 1. 优先用借用(&T / &mut T)而非获取所有权
fn process(data: &[i32]) -> i32 { // ✅ 借用
data.iter().sum()
}
// fn process(data: Vec<i32>) -> i32 { ... } // ❌ 不必要地获取所有权
// 2. 用 &str 代替 &String
fn greet(name: &str) { // ✅ 更通用
println!("Hello, {}!", name);
}
// fn greet(name: &String) { ... } // ❌ 不够通用
// 3. 需要修改参数时,用 &mut T
fn append_greeting(msg: &mut String) {
msg.push_str(" Welcome!");
}
// 4. 确实需要所有权时,明确说明
fn store_data(data: String) -> Config {
Config { data }
}
// 5. 用 clone() 时添加注释说明原因
let important = String::from("重要数据");
let copy = important.clone(); // 需要两份独立的数据
println!("{} / {}", important, copy);
}
struct Config {
data: String,
}
核心规则
概念 规则
所有权 每个值有且只有一个所有者
移动 赋值/传参时,非 Copy 类型的所有权转移
拷贝 Copy 类型(整数、浮点、bool、char、引用)赋值时复制
克隆 .clone() 深拷贝,开销可能很大
不可变借用 &T,可以多个同时存在
可变借用 &mut T,同一时刻只能有一个
互斥规则 同一时刻:要么多个 &T,要么一个 &mut T
悬垂引用 编译器禁止引用指向已释放的数据
切片 &sstart...end,不拥有数据的视图
生命周期 编译器确保引用不会比数据活得更久
函数参数 &str 优于 &String,&T 优于 &Vec
动手试试
补全下面的代码:
rust
// === 题目1:修复编译错误 ===
// 以下代码有多处所有权/借用错误,补全修复版本
// 补全:修复函数,使其能正确返回两个字符串中较长的一个
// 要求不获取所有权(使用借用)
fn longer_string(s1: &str, s2: &str) -> &str {
// 补全
todo!()
}
// 补全:修复函数,接收一个可变字符串引用,
// 在末尾追加 " (已验证)",然后返回字符串长度
fn verify_and_measure(s: &mut String) -> usize {
// 补全
todo!()
}
// === 题目2:实现 WordIndex ===
// 补全:WordIndex 结构体
// - 不拥有数据,只借用一个字符串切片
// - build(text: &str) -> WordIndex:构建索引
// 将文本按空白字符分割,记录每个单词的起始位置
// 返回 HashMap<&str, usize>,键为单词,值为起始位置
// - find(&self, word: &str) -> Option<usize>:查找单词位置
// - words(&self) -> Vec<&str>:返回所有单词列表(按出现顺序)
struct WordIndex<'a> {
text: &'a str,
index: std::collections::HashMap<&'a str, usize>,
order: Vec<&'a str>,
}
impl<'a> WordIndex<'a> {
fn build(text: &'a str) -> Self {
// 补全
todo!()
}
fn find(&self, word: &str) -> Option<usize> {
// 补全
todo!()
}
fn words(&self) -> Vec<&str> {
// 补全
todo!()
}
}
// === 题目3:实现 MessageQueue ===
// 补全:MessageQueue 结构体
// - 内部用 Vec<String> 存储消息
// - push(&mut self, msg: &str):添加消息(接受 &str,内部转为 String 存储)
// - pop(&mut self) -> Option<String>:取出最早的消息
// - peek(&self) -> Option<&str>:查看最早的消息(不取出,返回借用)
// - len(&self) -> usize:消息数量
// - contains(&self, keyword: &str) -> bool:是否包含某关键词的消息
struct MessageQueue {
messages: Vec<String>,
}
impl MessageQueue {
fn new() -> Self {
// 补全
todo!()
}
fn push(&mut self, msg: &str) {
// 补全
todo!()
}
fn pop(&mut self) -> Option<String> {
// 补全:返回最早的消息(index 0)
// 提示:如果 Vec 为空返回 None
todo!()
}
fn peek(&self) -> Option<&str> {
// 补全:返回最早消息的引用
todo!()
}
fn len(&self) -> usize {
// 补全
todo!()
}
fn contains(&self, keyword: &str) -> bool {
// 补全:检查是否有任何消息包含 keyword
todo!()
}
}
fn main() {
// === 测试 longer_string ===
let a = String::from("short");
let b = String::from("much longer string");
let result = longer_string(&a, &b);
println!("较长: {}", result); // 期望: much longer string
println!("a仍可用: {}", a); // ✅ a 未被移动
println!("b仍可用: {}", b); // ✅ b 未被移动
// === 测试 verify_and_measure ===
let mut msg = String::from("数据已加载");
let len = verify_and_measure(&mut msg);
println!("验证后: '{}' (长度: {})", msg, len);
// 期望: 验证后: '数据已加载 (已验证)' (长度: 11)
// === 测试 WordIndex ===
let text = String::from("hello world hello rust world");
let index = WordIndex::build(&text);
println!("'hello' 位置: {:?}", index.find("hello")); // 期望: Some(0)
println!("'world' 位置: {:?}", index.find("world")); // 期望: Some(6)
println!("'missing' 位置: {:?}", index.find("missing")); // 期望: None
println!("所有单词: {:?}", index.words());
// 期望: ["hello", "world", "rust"](去重,按首次出现顺序)
// === 测试 MessageQueue ===
let mut queue = MessageQueue::new();
queue.push("第一条消息");
queue.push("第二条消息");
queue.push("紧急:系统警告");
queue.push("第四条消息");
println!("队列长度: {}", queue.len()); // 期望: 4
println!("最早消息: {:?}", queue.peek()); // 期望: Some("第一条消息")
println!("包含'紧急': {}", queue.contains("紧急")); // 期望: true
println!("包含'错误': {}", queue.contains("错误")); // 期望: false
let first = queue.pop();
println!("弹出: {:?}", first); // 期望: Some("第一条消息")
println!("弹出后长度: {}", queue.len()); // 期望: 3
println!("现在最早: {:?}", queue.peek()); // 期望: Some("第二条消息")
}
期望输出:
较长: much longer string
a仍可用: short
b仍可用: much longer string
验证后: '数据已加载 (已验证)' (长度: 11)
'hello' 位置: Some(0)
'world' 位置: Some(6)
'missing' 位置: None
所有单词: "hello", "world", "rust"
队列长度: 4
最早消息: Some("第一条消息")
包含'紧急': true
包含'错误': false
弹出: Some("第一条消息")
弹出后长度: 3
现在最早: Some("第二条消息")
写好了贴过来,没问题就进入第31课:生命周期 Lifetime 进阶 👋
贴代码
完整参考答案(借用、生命周期、切片经典练习题)
rust
use std::collections::HashMap;
// === 题目1:修复编译错误 ===
fn longer_string(s1: &str, s2: &str) -> &str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
fn verify_and_measure(s: &mut String) -> usize {
s.push_str(" (已验证)");
s.len()
}
// === 题目2:实现 WordIndex ===
struct WordIndex<'a> {
text: &'a str,
index: HashMap<&'a str, usize>,
order: Vec<&'a str>,
}
impl<'a> WordIndex<'a> {
fn build(text: &'a str) -> Self {
let mut index = HashMap::new();
let mut order = Vec::new();
// split_whitespace 返回 &str,生命周期绑定输入text('a)
for word in text.split_whitespace() {
// entry API:只记录单词**第一次**出现的起始位置
index.entry(word).or_insert_with(|| {
// word.as_ptr() - text.as_ptr() 得到字节偏移
let pos = (word.as_ptr() as usize) - (text.as_ptr() as usize);
order.push(word);
pos
});
}
WordIndex { text, index, order }
}
fn find(&self, word: &str) -> Option<usize> {
self.index.get(word).copied()
}
fn words(&self) -> Vec<&'a str> {
self.order.clone()
}
}
// === 题目3:实现 MessageQueue ===
struct MessageQueue {
messages: Vec<String>,
}
impl MessageQueue {
fn new() -> Self {
MessageQueue {
messages: Vec::new(),
}
}
fn push(&mut self, msg: &str) {
self.messages.push(msg.to_string());
}
fn pop(&mut self) -> Option<String> {
if self.messages.is_empty() {
None
} else {
Some(self.messages.remove(0))
}
}
fn peek(&self) -> Option<&str> {
self.messages.first().map(|s| s.as_str())
}
fn len(&self) -> usize {
self.messages.len()
}
fn contains(&self, keyword: &str) -> bool {
self.messages.iter().any(|msg| msg.contains(keyword))
}
}
fn main() {
// === 测试 longer_string ===
let a = String::from("short");
let b = String::from("much longer string");
let result = longer_string(&a, &b);
println!("较长: {}", result); // 期望: much longer string
println!("a仍可用: {}", a); // ✅ a 未被移动
println!("b仍可用: {}", b); // ✅ b 未被移动
// === 测试 verify_and_measure ===
let mut msg = String::from("数据已加载");
let len = verify_and_measure(&mut msg);
println!("验证后: '{}' (长度: {})", msg, len);
// 期望: 验证后: '数据已加载 (已验证)' (长度: 11)
// === 测试 WordIndex ===
let text = String::from("hello world hello rust world");
let index = WordIndex::build(&text);
println!("'hello' 位置: {:?}", index.find("hello")); // 期望: Some(0)
println!("'world' 位置: {:?}", index.find("world")); // 期望: Some(6)
println!("'missing' 位置: {:?}", index.find("missing")); // 期望: None
println!("所有单词: {:?}", index.words());
// 期望: ["hello", "world", "rust"](去重,按首次出现顺序)
// === 测试 MessageQueue ===
let mut queue = MessageQueue::new();
queue.push("第一条消息");
queue.push("第二条消息");
queue.push("紧急:系统警告");
queue.push("第四条消息");
println!("队列长度: {}", queue.len()); // 期望: 4
println!("最早消息: {:?}", queue.peek()); // 期望: Some("第一条消息")
println!("包含'紧急': {}", queue.contains("紧急")); // 期望: true
println!("包含'错误': {}", queue.contains("错误")); // 期望: false
let first = queue.pop();
println!("弹出: {:?}", first); // 期望: Some("第一条消息")
println!("弹出后长度: {}", queue.len()); // 期望: 3
println!("现在最早: {:?}", queue.peek()); // 期望: Some("第二条消息")
}
运行输出
plaintext
较长: much longer string
a仍可用: short
b仍可用: much longer string
验证后: '数据已加载 (已验证)' (长度: 11)
'hello' 位置: Some(0)
'world' 位置: Some(6)
'missing' 位置: None
所有单词: "hello", "world", "rust"
队列长度: 4
最早消息: Some("第一条消息")
包含'紧急': true
包含'错误': false
弹出: Some("第一条消息")
弹出后长度: 3
现在最早: Some("第二条消息")
逐题知识点讲解
题目一(1)longer_string
- 参数是两个不可变借用 &str,函数直接返回其中一个借用;
- 返回值生命周期由输入参数自动推断,两个借用生命周期一样就可以编译通过;
- 没有发生所有权转移,a、b 在调用之后仍然可用。
题目一(2)verify_and_measure
- &mut String :可变借用,可以原地修改字符串;
- push_str :追加文本,不会产生新字符串;
- 返回 usize (Copy类型,直接返回值),不返回引用。
易错坑:新手容易写成 return s; ,尝试返回可变引用,本题不需要。
题目二: WordIndex(生命周期标记 <'a> 核心考题)
关键点
- WordIndex<'a> :结构体不拥有字符串数据,只是借用外部text,生命周期参数 'a ;
- HashMap 的键是 &'a str ,切片借用原字符串,没有复制,非常节省内存;
- entry.or_insert_with :保证只存单词第一次出现;
- 字节偏移计算: word.as_ptr() - text.as_ptr() ,得到单词相对于文本开头的字节索引;
- order 数组保存首次出现顺序,words() 返回去重后的单词列表。
⚠️ 生命周期大坑:WordIndex 不能活得比原始的 text 变量更长,否则悬垂引用,编译器会检查。
题目三: MessageQueue(所有权 vs 借用对比)
- 内部存储 Vec :拥有消息所有权;push接收 &str ,调用 to_string() 把数据拷贝到堆;
- pop: remove(0) ,把字符串所有权移出Vec,返回Option;
remove(0) 时间复杂度O(n),这是简易队列;高性能生产队列建议 VecDeque ;
- peek:返回 Option<&str> ,只是借用,所有权仍然保存在队列内部;
- contains: iter().any() ,遍历每条消息,调用str::contains子串匹配。
主线学习进度小结
到此:生命周期 + 借用切片 的专项练习完成。
整条Rust学习路线:基础语法 → 所有权/借用 → 生命周期 → 泛型&Trait → 标准容器 → 自定义错误 → Rc‑RefCell‑Weak → Unsafe MyVec → Tokio异步编程。