基础学习
Mac 安装
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
source "$HOME/.cargo/env"
Rustc
Rustc 是 Rust 的编译器,可以将 Rust 代码编译成可执行文件。你可以使用以下命令来编译 Rust 代码:
fn main() {
println!("Hello, world!");
}
# 编译 Rust 代码
rustc main.rs
# 运行编译后的可执行文件
./main
Cargo
Cargo 是 Rust 的包管理器和构建系统,可以帮助你管理项目的依赖、构建和发布。你可以使用以下命令来创建一个新的 Rust 项目:
cargo new my_project
cd study
tree study
# └── study
# ├── Cargo.toml
# └── src
# └── main.rs
Cargo.toml
Cargo.toml是 Cargo 项目的配置文件,用于指定项目的元数据和依赖项。src/main.rs 是项目的主入口文件,包含了 Rust 代码。包含名称,版本和依赖项等信息。
Cargo 命令
cargo new:编译项目。cargo build:编译项目。cargo run:编译并运行项目。cargo test:运行项目的测试。cargo clean:清理项目生成的文件。cargo check: 快速检测项目中的错误, 而不是执行完整的编译过程。cargo build --release:优化速度的编译,然后发布。
猜数游戏
// 用到的库
// 用于比较大小
use std::cmp::Ordering;
// 用于输入输出
use std::io;
// 用于生成随机数
use rand::Rng;
fn main() {
// 输出欢迎信息
println!("Guess the number!");
// 生成一个随机数,范围在 1 到 100 之间
let secret_number = rand::thread_rng().gen_range(1..=100);
// 进入游戏循环
loop {
println!("Please input your guess.");
// 创建一个可变的字符串变量来存储用户的输入
let mut guess = String::new();
// 从标准输入读取用户的输入,并将其存储在 guess 变量中
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
// 将用户输入的字符串转换为一个无符号 32 位整数
let guess: u32 = match guess.trim().parse() {
// 如果转换成功,返回数字
Ok(num) => num,
// 如果转换失败,继续下一次循环
Err(_) => continue,
};
println!("You guessed: {guess}");
// 比较用户的猜测和秘密数字,并输出相应的提示信息
match guess.cmp(&secret_number) {
// 如果用户的猜测小于秘密数字,输出 "Too small!"
Ordering::Less => println!("Too small!"),
// 如果用户的猜测大于秘密数字,输出 "Too big!"
Ordering::Greater => println!("Too big!"),
// 如果用户的猜测等于秘密数字,输出 "You win!" 并退出循环
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
变量
Rust 中的变量默认是不可变的,这意味着一旦一个值被绑定到一个变量上,就不能再改变这个值。如果你想要一个可变的变量,可以使用 mut 关键字来声明它。以下是一些示例:
不可变变量和可变变量
fn main() {
// 不可变变量
let x = 5;
println!("The value of x is: {x}");
// x = 6; // 这行代码会导致编译错误,因为 x 是不可变的
let mut y = 10; // 可变变量
println!("The value of y is: {y}");
y = 15; // 这行代码是合法的,因为 y 是可变的
println!("The value of y is: {y}");
// 常量
// 常量必须指定类型,并且在编译时就必须知道它的值 且不能被修改
const MAX_POINTS: u32 = 100_000;
}
变量遮蔽
fn main() {
// 初始化变量 x,值为 5
let x = 5;
// 使用 let 重新声明并隐藏之前的 x,值为 5 + 1 = 6
let x = x + 1;
// 进入内部作用域
{
// 再次用 let 隐藏外层的 x,值为 6 * 2 = 12
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
} // 内部作用域结束,内部的 x 被销毁
// 这里的 x 仍然是外层的 6
println!("The value of x is: {x}");
}
数据类型
标量类型
Rust 中的标量类型包括整数、浮点数、布尔值和字符。
整数
Rust 提供了多种整数类型,分为有符号和无符号两类。常见的整数类型包括:
i8,i16,i32,i64,i128:有符号整数,分别占用 8、16、32、64 和 128 位。u8,u16,u32,u64,u128:无符号整数,分别占用 8、16、32、64 和 128 位。
整数溢出会报错
fn main() {
let x: u8 = 255; // u8 的最大值是 255
let y = x + 1; // 这行代码会导致编译错误,因为 y 的值会溢出 u8 的范围
println!("The value of y is: {y}");
}
浮点数
Rust 提供了两种浮点数类型:
f32:单精度浮点数,占用 32 位。f64:双精度浮点数,占用 64 位。
数值运算
Rust 支持基本的数值运算,包括加法、减法、乘法、除法和取余。以下是一些示例:
fn main() {
let sum = 5 + 10; // 加法
let difference = 95.5 - 4.3; // 减法
let product = 4 * 30; // 乘法
let quotient = 56.7 / 32.2; // 除法
let remainder = 43 % 5; // 取余
println!("The sum is: {sum}");
println!("The difference is: {difference}");
println!("The product is: {product}");
println!("The quotient is: {quotient}");
println!("The remainder is: {remainder}");
}
布尔值
Rust 中的布尔值类型是 bool,它有两个可能的值:true 和 false。布尔值通常用于条件判断和控制流。以下是一些示例:
fn main() {
let is_rust_fun = true; // 布尔值为 true
let is_boring = false; // 布尔值为 false
if is_rust_fun {
println!("Rust is fun!");
} else {
println!("Rust is boring.");
}
}
字符
Rust 中的字符类型是 char,它表示一个 Unicode 字符,占用 4 字节。你可以使用单引号来定义一个字符。以下是一些示例:
fn main() {
let c = 'z'; // 定义一个字符
let heart_eyed_cat = '😻'; // 定义一个 Unicode 字符
println!("The character is: {c}");
println!("The heart-eyed cat is: {heart_eyed_cat}");
}
复合类型
Rust 中的复合类型包括元组和数组。
元组
元组是一种将多个值组合成一个复合类型的方式,长度固定。元组中的值可以是不同类型的。以下是一些示例:
// 此处时值传递,元组中的值会被复制到函数中
fn show(tup: (i32, f64, &str)) {
println!("传入的字符串是: {}", tup.2);
}
fn main(){
// 元组定义了不可变的集合,可以包含不同类型的值
// 定义 let tuple = (xxxx)
let tuple: (i32, f64, &str) = (42, 3.14, "Hello, Rust!");
println!("元组内容: {:?}", tuple);
// 访问元组元素
show(tuple);
// 上面函数是值传递,元组中的值会被复制到函数中,所以我们仍然可以在函数外部使用元组
println!("元组类型: {}", std::any::type_name::<(i32, f64, &str)>());
// 解构元组
let (par1, par2, par3) = tuple;
println!("解构后的值: {}, {}, {}", par1, par2, par3);
}
数组
数组是一种将多个值组合成一个复合类型的方式,长度固定。数组中的值必须是相同类型的。以下是一些示例:
// 值传递
fn show(mut arr: [i32; 5]) {
println!("传入的数组是: {:?}", arr);
for i in 0..arr.len() {
arr[i] = arr[i] * 2; // 修改数组元素
println!("show_array {}: {}", i, arr[i]);
}
}
fn change(arr: &mut [i32; 5]) {
println!("传入的数组是: {:?}", arr);
for i in 0..arr.len() {
arr[i] = arr[i] * 2; // 修改数组元素
println!("change_array {}: {}", i, arr[i]);
}
}
fn main(){
// 数组 是用来存储一系列数据,拥有相同类型 T 的对象的集合,在内存中是连续存储的。使用中括号 [] 来创建,且它们的大小在编译时会被确定。数组下标是从0 开始。数组是在栈中分配的,数组可以自动被借用成为 切片(slice)。
// 定义一个包含5个元素的数组,元素类型为i32
let arr: [i32; 5] = [1, 2, 3, 4, 5];
println!("数组内容: {:?}", arr);
println!("数组类型: {}", std::any::type_name::<[i32; 5]>());
println!("数组长度: {}", arr.len());
println!("数组第一个元素: {}", arr[0]);
// 使用for循环遍历数组元素
for i in 0..arr.len() {
println!("数组元素 {}: {}", i, arr[i]);
}
// with index
for (index, value) in arr.iter().enumerate() {
println!("with index {}: {}", index, value);
}
// without index
for value in arr.iter() {
println!("without index: {}", value);
}
// 将数组传递给函数,进行值传递
show(arr);
for value in arr.iter() {
println!("without index: {}", value);
}
let mut change_arr: [i32; 5] = [1, 2, 3, 4, 5];
println!("change_arr before change: {:?}", change_arr);
change(& mut change_arr);
println!("change_arr before change: {:?}", change_arr);
}
函数
函数是 Rust 中的基本代码块,用于执行特定的任务。函数可以接受参数并返回值。以下是一些示例:
加冒号 函数定义格式:fn 函数名(参数列表) -> 返回类型 { 函数体 }
fn hello() {
println!("Hello, rust!");
}
// function with return
fn return_string_with_return() -> String {
return String::from("Hello, rust!");
}
// 没有return语句,最后一个表达式的值会被隐式返回
// 不加分号,表示这是一个表达式,函数会返回这个表达式的值
fn return_string_without_return() -> String {
String::from("Hello, rust!")
}
// 函数参数示例
fn fn_with_parameters(a: i32, b: i32) -> i32 {
a + b
}
// 值传递
fn value_passing(mut x: i32) {
x = x * 2 ; // 修改x的值
println!("函数内部的x: {}", x); // 输出x的值
}
// 引用传递
fn reference_passing(x: &mut i32) {
// *x表示解引用,获取x指向的值
*x = *x * 2; // 修改x的值
println!("函数内部的x: {}", x); // 输出x的值
}
fn show(par: String) {
println!("传入的字符串是: {}", par);
}
fn main() {
hello();
let s = return_string_with_return();
println!("{}", s);
let s = return_string_without_return();
println!("{}", s);
let sum = fn_with_parameters(5, 10);
println!("5 + 10 = {}", sum);
let x = 10;
println!("函数外部的x: {}", x); // 输出x的值
value_passing(x);
println!("函数外部的x仍然是: {}", x); // 输出x的
let mut y = 10;
println!("函数外部的y: {}", y); // 输出y的值
reference_passing(&mut y);
println!("函数外部的y现在是: {}", y); // 输出y的值
let par = String::from("Hello, Rust!");
show(par);
// 这行会导致编译错误,因为par已经被move到函数show中,无法再使用
// println!("函数外部的par: {}", par); // 输出par的值
}
注释
Rust 支持两种类型的注释:行注释和块注释。
- 行注释以
//开头,适用于单行注释。 - 块注释以
/*开始,以*/结束,适用于多行注释。以下是一些示例:
fn main() {
// 这是一个行注释
println!("Hello, world!"); /* 这是一个块注释 */
/*
这是一个多行块注释
可以包含多行文本
*/
}
语句和表达式
Rust 中的语句是执行某些操作但不返回值的代码块,而表达式是计算并返回一个值的代码块。以下是一些示例:
fn main() {
let x = 5; // 这是一个语句,声明了一个变量 x 并赋值为 5
let y = { // 这是一个表达式,计算并返回一个值
let z = x + 10; // 这是一个语句,声明了一个变量 z 并赋值为 x + 10
z * 2 // 这是一个表达式,计算并返回 z 的值乘以 2
};
println!("The value of y is: {y}"); // 输出 y 的值
}
控制流
Rust 提供了多种控制流结构,包括 if 表达式、循环和模式匹配。
if 表达式
fn main() {
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
}
循环
loop 循环
fn main() {
let mut count = 0;
loop {
count += 1;
println!("Count: {count}");
if count >= 5 {
break; // 退出循环
}
}
定义循环
fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}
count += 1;
}
println!("End count = {count}");
}
while 循环
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
}
for 循环
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
let mut array = [1, 2, 3, 4, 5];
for i in 0..array.len() {
array[i] += 1;
println!("array[{}] = {}", i, array[i]);
}
}
所有权
所有权是 Rust 的核心概念之一,它管理着内存的分配和释放。每个值在 Rust 中都有一个所有者,当所有者离开作用域时,值会被自动释放。以下是一些示例:
所有权三原则
- Rust 中的每个值都有一个所有者。
- 每个值同时只能有一个所有者。
- 当所有者离开作用域时,值会被自动释放。
int 类型的变量具有 Copy trait,可以被复制而不是移动,因此它们在赋值时不会失去所有权。
那么,哪些类型实现了该Copy特性呢?您可以查看给定类型的文档来确认,但一般来说,任何简单的标量值组都可以实现该特性Copy,而任何需要分配内存或属于某种资源类型的类型都不能实现该特性Copy。以下是一些实现了该特性的类型Copy:
- 所有整数类型(如 i32、u64 等)
- 所有浮点数类型(如 f32、f64)
- 布尔类型(bool)
- 字符类型(char)
- 元组类型(当且仅当所有元素都实现了 Copy 特性时)
// 栈 后进先出 类型大小固定
// 堆 先进先出 类型大小不固定
// 基本的类型 实现了Copy trait 赋值时会复制数据
// 如果基本类型里嵌套了复杂类型 那么这个基本类型就不再实现Copy trait 赋值时会移动数据
// 复杂的类型 没有实现Copy trait 赋值时会移动数据
fn show_number(num: i32) {
println!("传入的数字是: {}", num);
}
fn show_vector(vec: Vec<i32>) {
println!("传入的向量是: {:?}", vec);
}
// 所有权转移,vec的所有权被转移到函数中,函数内部使用的是vec的副本
fn show_vector_return(vec: Vec<i32>) -> Vec<i32> {
println!("传入的向量是: {:?}", vec);
vec // 返回vec,vec的所有权被转移到调用者
}
// 借用,vec的所有权没有被转移到函数中,函数内部使用的是vec的借用
fn show_borrowed_vector(vec: &Vec<i32>) {
println!("传入的借用向量是: {:?}", vec);
}
fn show_mut_borrowed_vector(vec: &mut Vec<i32>) {
println!("before: {:?}", vec);
vec.push(4); // 修改借用的向量
println!("after: {:?}", vec);
}
fn main(){
let x = 5; // x在栈上分配
let y = x; // y在栈上分配,x的值被复制给y
println!("x的值: {}, y的值: {}", x, y); // 输出x的值: 5, y的值: 5
show_number(x); // x的值被复制到函数中,函数内部使用的是x的副本
show_number(y); // y的值被复制到函数中,函数内部使用的是y的副本
println!("x的值: {}", x);
println!("y的值: {}", y);
let vec_move = vec![1, 2, 3]; // vec_move在堆上分配,包含一个指向堆数据的指针、长度和容量
let moved_vec = vec_move; // vec_move被移动到moved_vec,vec_move不再可用
println!("moved_vec: {:?}", moved_vec);
// println!("vec_move: {:?}", vec_move); // 这行会导致编译错误,因为vec_move已经被移动了
// 没有&号,所有权转移,且未实现COpy
let returned_vec = show_vector_return(moved_vec); // moved_vec被移动到函数中,函数内部使用的是moved_vec的副本
println!("returned_vec: {:?}", returned_vec);
let mut vec_borrow = vec![1, 2, 3]; // vec_borrow在堆上分配
// 有个& 号
show_borrowed_vector(&vec_borrow); // 传递vec_borrow的借用
println!("vec_borrow after borrowing: {:?}", vec_borrow); // vec_borrow仍然可用,因为它的所有权没有被转移
show_mut_borrowed_vector(&mut vec_borrow); // 传递vec_borrow的可变借用
println!("vec_borrow after mutable borrowing: {:?}", vec_borrow); // vec_borrow被修改了,因为它的可变借用允许修改数据
}
引用和借用
借用只能有一个可变引用,或者任意数量的不可变引用,但不能同时拥有可变引用和不可变引用。这是为了防止数据竞争和保证内存安全。
引用
引用是 Rust 中的一种机制,允许你在不获取所有权的情况下访问一个值。借用是指通过引用来使用一个值,而不获取它的所有权。以下是一些示例:
fn main() {
let s1 = String::from("hello"); // s1 是一个 String 类型的变量,拥有 "hello" 这个字符串的所有权
let len = calculate_length(&s1); // 通过引用借用 s1 的值,而不获取它的所有权
println!("The length of '{s1}' is {len}."); // 输出 "The length of 'hello' is 5."
}
fn calculate_length(s: &String) -> usize {
s.len()
}
借用
引用是不可变的,意味着你不能通过引用来修改一个值。如果你想要一个可变的引用,可以使用 mut 关键字来声明它。以下是一些示例:
fn main() {
let mut s = String::from("hello"); // s 是一个可变的 String 类型的变量,拥有 "hello" 这个字符串的所有权
change(&mut s); // 通过可变引用借用 s 的值,并修改它
println!("The modified string is: {s}"); // 输出 "The modified string is: hello, world!"
}
fn change(some_string: &mut String) {
some_string.push_str(", world!"); // 修改 some_string 的值
}
总结
fn xxx(par)→ 所有权转移(如果类型未实现Copy)fn xxx(&par)→ 不可变借用,所有权不转移fn xxx(&mut par)→ 可变借用,所有权不转移
切片
切片是一种引用类型,允许你在不获取所有权的情况下访问一个集合的一部分。切片的长度不是固定的,它可以引用集合的一部分。切片的语法是 &collection[start..end],其中 start 是切片的起始索引,end 是切片的结束索引(不包含)。
切片本身只是一个视图,它并不拥有数据的所有权。当切片引用一个集合时,它只是指向集合中的一个连续序列。
切片和引用的区别在于:引用只是借用了整个值,而切片则允许你借用了值的一部分。
切片分为不可变切片和可变切片两种。不可变切片允许你读取数据,但不能修改数据;可变切片则允许你修改数据。
字符串切片
字符串切片是对 String 或字符串字面量的引用,它指向一个 UTF-8 编码的字符串序列。字符串切片的类型是 &str。
fn main() {
let mut vector = Vec::new();
vector.push(1);
vector.push(2);
vector.push(3);
println!("vector: {:?}", vector);
// 开始到结束
println!("切片内容: {:?}", &vector[1..]);
// 开始到3 左闭右开
println!("切片内容: {:?}", &vector[1..3]);
// 取前两个元素
println!("切片内容: {:?}", &vector[..2]);
show_slice(&vector); // 传递切片的借用
println!("vector after slicing: {:?}", vector); // vector仍然可用,因为它的所有权没有被转移
let mut vector_mut = vec![1, 2, 3];
// 仅仅改变前两个元素
show_mut_slice(&mut vector_mut[..2]); // 传递可变切片的借
println!("vector_mut after mutable slicing: {:?}", vector_mut); // vector_mut被修改了,因为它的可变借用被传递到函数中
}
fn show_slice(slice: &[i32]) {
println!("传入的切片是: {:?}", slice);
for i in 0..slice.len() {
println!("show_slice {}: {}", i, slice[i]);
}
}
fn show_mut_slice(slice: &mut [i32]) {
println!("传入的可变切片是: {:?}", slice);
for i in 0..slice.len() {
slice[i] = slice[i] * 2; // 修改切片元素
println!("show_mut_slice {}: {}", i, slice[i]);
}
}
字符串字面量就是切片
字符串字面量(如 "hello")是 &str 类型的切片,它直接指向二进制程序中的某个位置。因此,字符串字面量是一个切片,而不是 String。
fn main() {
let s: &str = "Hello, world!"; // s 是一个 &str 类型的切片,指向二进制程序中的 "Hello, world!"
println!("s = \"{}\"", s); // s = "Hello, world!"
// 字符串字面量是不可变的,因为它是 &str 类型
// let s2 = "hello"; // s2 是不可变的
// s2 = "world"; // 错误:s2 是不可变的
}
切片作为参数
将切片作为参数传递是一种更灵活的编程风格。当你有一个函数需要操作字符串时,使用 &str 而不是 &String 作为参数类型,可以同时接受 String 和字符串字面量。
fn main() {
let s = String::from("hello world");
// 使用 &str 作为参数,可以接受 String 或字符串字面量
let word = first_word(&s); // 传入 &String
println!("The first word is: {}", word); // The first word is: hello
let literal = "hello world";
let word2 = first_word(literal); // 传入 &str
println!("The first word is: {}", word2); // The first word is: hello
// 使用 &str 作为参数,可以同时接受 String 和字符串字面量
let word3 = first_word(&s[..6]);
println!("The first word is: {}", word3); // The first word is: hello
}
fn first_word(s: &str) -> &str {
// 将 &str 作为参数,可以同时接受 String 和字符串字面量
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
可变切片
可变切片允许你修改切片引用的数据。通过可变切片,你可以修改数组或字符串的部分内容。
fn main() {
let mut s = String::from("hello world");
// 获取可变字符串切片
let slice = &mut s[..5];
println!("Before: {}", slice); // Before: hello
// 通过可变切片修改数据
slice.make_ascii_uppercase();
println!("After: {}", slice); // After: HELLO
// 注意:修改后原字符串也会改变
println!("Original s: {}", s); // Original s: HELLO world
}
可变切片的规则和可变引用一样:一个可变引用 OR 任意数量的不可变引用,不能同时拥有可变引用和不可变引用。
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
// 错误示例:同时拥有可变和不可变引用
let slice = &mut v[..3];
// let _immutable = &v[..]; // 错误:不能同时拥有可变引用和不可变引用
println!("slice = {:?}", slice);
// 正确示例:在使用可变引用之前,先完成所有可变引用的使用
let slice = &mut v[..3];
slice[0] = 10;
println!("slice = {:?}", slice); // slice = [10, 2, 3]
// 现在可以使用不可变引用了
let _immutable = &v[..];
println!("v = {:?}", v); // v = [10, 2, 3, 4, 5]
}
其他类型的切片
切片不仅适用于字符串,还适用于数组和其他集合类型。
fn main() {
// 数组切片
let a = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..4]; // 引用数组的一个切片
println!("slice = {:?}", slice); // slice = [2, 3, 4]
// 切片的长度
println!("slice len = {}", slice.len()); // slice len = 3
// 切片是否为空
let empty_slice: &[i32] = &a[0..0];
println!("empty_slice is empty: {}", empty_slice.is_empty()); // empty_slice is empty: true
// 遍历切片
for item in slice {
println!("item = {}", item);
}
// 切片迭代器
let slice = &a[1..4];
let sum: i32 = slice.iter().sum();
println!("sum = {}", sum); // sum = 9
// 使用 first() 和 last()
println!("first = {:?}", slice.first()); // first = Some(2)
println!("last = {:?}", slice.last()); // last = Some(4)
// 使用 get() 方法安全访问
println!("slice.get(0) = {:?}", slice.get(0)); // slice.get(0) = Some(2)
println!("slice.get(10) = {:?}", slice.get(10)); // slice.get(10) = None
}
Boxed 切片
Box 是 Rust 中的智能指针类型,它可以在堆上分配数据。Box<[T]> 是一个堆分配的切片,它指向一个切片而不是整个数组。
fn main() {
// Box<[T]> - 堆分配的切片,大小固定但长度可变
let boxed: Box<[i32]> = Box::new([1, 2, 3, 4, 5]);
println!("boxed slice = {:?}", boxed); // boxed slice = [1, 2, 3, 4, 5]
println!("boxed len = {}", boxed.len()); // boxed len = 5
// 使用 into_vec() 将 Box<[T]> 转换为 Vec<T>
let vec: Vec<i32> = boxed.into_vec();
println!("vec = {:?}", vec); // vec = [1, 2, 3, 4, 5]
// Box<str> - 堆分配的字符串切片
let boxed_str: Box<str> = Box::new("hello world");
println!("boxed_str = {}", boxed_str); // boxed_str = hello world
// Box<[T]> 用于函数返回不确定大小的数据
let result = create_boxes();
println!("result = {:?}", result); // result = [1, 2, 3]
}
fn create_boxes() -> Box<[i32]> {
Box::new([1, 2, 3])
}
注意:Box<[T]> 和 Box<[T; N]> 是不同的。Box<[T]> 是堆分配的可变长度切片,而 Box<[T; N]> 是堆分配的固定大小数组。
fn main() {
// Box<[T; 5]> - 堆分配的固定大小数组(数组本身在堆上,但长度是固定的)
let boxed_array: Box<[i32; 5]> = Box::new([1, 2, 3, 4, 5]);
println!("boxed_array = {:?}", boxed_array); // boxed_array = [1, 2, 3, 4, 5]
println!("boxed_array len = {}", boxed_array.len()); // boxed_array len = 5
// Box<[T]> - 堆分配的可变长度切片
let boxed_slice: Box<[i32]> = Box::new([1, 2, 3]);
println!("boxed_slice = {:?}", boxed_slice); // boxed_slice = [1, 2, 3]
println!("boxed_slice len = {}", boxed_slice.len()); // boxed_slice len = 3
}
切片的实际应用:实现 trim 和 split
fn main() {
// 自定义 trim 函数,去除字符串首尾的空格
let s = " hello world ";
let trimmed = custom_trim(s);
println!("trimmed = \"{}\"", trimmed); // trimmed = "hello world"
// 自定义 split 函数,按空格分割字符串
let s = "hello world rust";
let words = custom_split(s);
println!("words = {:?}", words); // words = ["hello", "world", "rust"]
}
fn custom_trim(s: &str) -> &str {
let bytes = s.as_bytes();
let mut start = 0;
let mut end = bytes.len();
// 找到第一个非空字符
while start < end && bytes[start] == b' ' {
start += 1;
}
// 找到最后一个非空字符
while end > start && bytes[end - 1] == b' ' {
end -= 1;
}
&s[start..end]
}
fn custom_split(s: &str) -> Vec<&str> {
let mut result = Vec::new();
let mut start = 0;
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
if start < i {
result.push(&s[start..i]);
}
start = i + 1;
}
}
// 添加最后一个单词
if start < bytes.len() {
result.push(&s[start..]);
}
result
}
切片的注意事项
- 切片的索引必须在有效范围内,否则会导致 panic
- 字符串切片的索引必须落在 UTF-8 字符边界上
- 切片不拥有数据的所有权,当原数据被释放时,切片将变得无效
- 可变切片和不可变切片不能同时存在(借用规则)
fn main() {
// 切片引用的数据不能在被释放后继续使用
let s = String::from("hello");
let slice = &s[..];
drop(s); // 释放 s
// println!("{}", slice); // 错误:slice 引用的数据已经被释放
}
struct
定义结构体
结构体是一种自定义数据类型,用于将多个相关的数据组合在一起。与元组不同,结构体可以为每个数据字段指定名称,使数据更加清晰易读。
结构体使用 struct 关键字定义,名称采用 PascalCase 风格。结构体的每个字段都需要指定类型。
// 定义一个表示用户信息的结构体
struct User {
username: String, // 用户名
email: String, // 邮箱
sign_in_count: u64, // 登录次数
active: bool, // 是否活跃
}
fn main() {
// 创建结构体实例
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
// 访问结构体字段
println!("Username: {}", user1.username);
println!("Email: {}", user1.email);
println!("Active: {}", user1.active);
println!("Sign in count: {}", user1.sign_in_count);
}
创建实例时的字段初始化简写
当结构体实例的字段名与变量名相同时,可以省略字段名,直接使用变量名。这种简写方式在构造函数中非常有用。
fn main() {
let email = String::from("test@example.com");
let username = String::from("testuser");
// 字段名与变量名相同时,可以简写
let user = User {
email, // 等价于 email: email
username, // 等价于 username: username
active: true,
sign_in_count: 1,
};
println!("User: {} - {}", user.username, user.email);
}
结构体更新语法
使用结构体更新语法,可以基于已有实例创建新实例。通过 ..实例名 的方式,可以指定需要覆盖的字段,其余字段使用已有实例的值。
fn main() {
let user1 = User {
email: String::from("user1@example.com"),
username: String::from("user1"),
active: true,
sign_in_count: 1,
};
// 使用更新语法创建新实例,只修改 email 字段
let user2 = User {
email: String::from("user2@example.com"),
..user1 // 其余字段从 user1 复制
};
println!("user1: {} - {}", user1.username, user1.email);
println!("user2: {} - {}", user2.username, user2.email);
println!("user2 active: {} (copied from user1)", user2.active);
}
注意:所有权转移的字段(如 String)不能直接复制,必须显式创建新的值。
fn main() {
let user1 = User {
email: String::from("user1@example.com"),
username: String::from("user1"),
active: true,
sign_in_count: 1,
};
// 错误:username 是 String 类型,不能直接复制
// let user2 = User {
// username: user1.username, // 这里会转移所有权
// ..user1
// };
// 正确:显式创建新的 String
let user2 = User {
email: String::from("user2@example.com"),
username: String::from("user2"), // 创建新的 String
active: user1.active,
sign_in_count: user1.sign_in_count,
};
println!("user1 username: {}", user1.username); // user1.username 仍然有效
println!("user2 username: {}", user2.username);
}
元组结构体
元组结构体是一种没有字段名的结构体,类似于元组,但具有自己的类型名称。适用于需要命名一个元组类型但又不需要具体字段名的场景。
// 定义一个二维坐标的元组结构体
struct Color(i32, i32, i32);
// 定义一个二维点的元组结构体
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// 访问元组结构体的字段
println!("Black color: R={}, G={}, B={}", black.0, black.1, black.2);
println!("Origin point: X={}, Y={}, Z={}", origin.0, origin.1, origin.2);
// 解构元组结构体
let Color(r, g, b) = black;
println!("Destructured: r={}, g={}, b={}", r, g, b);
}
类单元结构体
没有任何字段的结构体称为类单元结构体(Unit-like Structs)。这种结构体常用于实现某种 trait,或者作为某种标记类型。
// 定义一个类单元结构体
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
println!("AlwaysEqual instance created");
}
// 类单元结构体常用作标记或实现 trait
struct Marker;
示例程序
// 定义矩形结构体
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// 结构体方法示例
impl User {
// 关联函数,类似于静态方法
fn new(username: String, email: String) -> User {
User {
username,
email,
sign_in_count: 0,
active: true,
}
}
fn just_test(username: String, email: String) -> String {
format!("Username: {}, Email: {}", username, email)
}
// 实例方法,使用&self参数来访问结构体的字段
fn get_username(&self) -> &str {
&self.username
}
}
fn show(user: User) {
println!("传入的用户信息: {}, {}, {}, {}", user.username, user.email, user.sign_in_count, user.active);
}
fn show_ref(user: &User) {
println!("传入的用户信息: {}, {}, {}, {}", user.username, user.email, user.sign_in_count, user.active);
}
fn show_mut_ref(user: &mut User) {
user.sign_in_count += 1; // 修改用户的登录次数
println!("传入的用户信息: {}, {}, {}, {}", user.username, user.email, user.sign_in_count, user.active);
}
fn get_user() -> User {
User {
username: String::from("Bob"),
email: String::from("bob@example.com"),
sign_in_count: 0,
active: false,
}
}
fn main(){
let user1 = User {
username: String::from("Alice"),
email: String::from("alice@example.com"),
sign_in_count: 1,
active: true,
};
println!("User: {}, Email: {}, Sign-in Count: {}, Active: {}", user1.username, user1.email, user1.sign_in_count, user1.active);
show(user1); // 传递user1的所有权到函数中
// println!("User: {}, Email: {}, Sign-in Count: {}, Active: {}", user1.username, user1.email, user1.sign_in_count, user1.active); // 这行会导致编译错误,因为user1的所有权已经被转移了
let mut user2 = User {
username: String::from("Alice"),
email: String::from("alice@example.com"),
sign_in_count: 999,
active: true,
};
show_mut_ref(&mut user2); // 传递user2的引用到函数中
println!("User: {}, Email: {}, Sign-in Count: {}, Active: {}", user2.username, user2.email, user2.sign_in_count, user2.active);
let mut user3 = get_user(); // 从函数中获取一个User实例
println!("User: {}, Email: {}, Sign-in Count: {}, Active: {}", user3.username, user3.email, user3.sign_in_count, user3.active);
show_mut_ref(&mut user3); // 传递user2的引用到函数中
println!("User: {}, Email: {}, Sign-in Count: {}, Active: {}", user3.username, user3.email, user3.sign_in_count, user3.active);
// 静态方法调用
let user4 = User::new(String::from("Lsr"), String::from("charlie@example.com"));
println!("User: {}, Email: {}, Sign-in Count: {}, Active: {}", user4.username, user4.email, user4.sign_in_count, user4.active);
println!("Username from method: {}", user4.get_username());
println!("Just test: {}", User::just_test(String::from("Dave"), String::from("NB")));
}
枚举与模式匹配
1. 定义枚举 (Defining an Enum)
枚举是一种自定义类型,用于列举所有可能的值。枚举让类型更安全,可以避免使用魔法值。
1.1 基本枚举定义
fn main() {
// 定义一个简单的枚举,表示扑克牌的花色
enum Suit {
Spades, // 黑桃
Hearts, // 红心
Diamonds, // 方块
Clubs, // 梅花
}
// 创建枚举值的实例
let card_suit = Suit::Hearts;
// 枚举值可以用于模式匹配
match card_suit {
Suit::Spades => println!("黑桃"),
Suit::Hearts => println!("红心"),
Suit::Diamonds => println!("方块"),
Suit::Clubs => println!("梅花"),
}
}
1.2 枚举值关联数据
枚举的每个变体都可以关联额外的数据,这是枚举相比简单常量的强大之处。
fn main() {
// 枚举变体可以关联不同类型和数量的数据
enum Message {
Quit, // 没有关联数据
Move { x: i32, y: i32 }, // 关联一个结构体
Write(String), // 关联一个字符串
ChangeColor(i32, i32, i32), // 关联三个整数
}
// 创建不同变体的实例
let m1 = Message::Quit;
let m2 = Message::Move { x: 10, y: 20 };
let m3 = Message::Write(String::from("hello"));
let m4 = Message::ChangeColor(255, 128, 0);
// 通过模式匹配处理不同变体
fn process_message(msg: Message) {
match msg {
Message::Quit => {
println!("退出程序");
}
Message::Move { x, y } => {
println!("移动到坐标 ({x}, {y})");
}
Message::Write(text) => {
println!("写入文本: {text}");
}
Message::ChangeColor(r, g, b) => {
println!("改变颜色为 RGB({r}, {g}, {b})");
}
}
}
process_message(m2);
}
1.3 使用结构体实现类似功能
对比:使用多个结构体实现相同功能
fn main() {
// 使用结构体模拟枚举
struct QuitMessage;
struct MoveMessage { x: i32, y: i32 }
struct WriteMessage(String);
struct ChangeColorMessage(i32, i32, i32);
// 缺点:需要为每种消息定义独立的类型,且处理函数签名不同
fn process_quit(_: QuitMessage) {}
fn process_move(_: MoveMessage) {}
// ...
}
1.4 枚举的方法
枚举可以像结构体一样定义方法。
fn main() {
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
// 为枚举定义方法
impl Message {
fn call(&self) {
// 使用 match 处理不同变体
match self {
Message::Quit => println!("Quit called"),
Message::Move { x, y } => println!("Move called to ({x}, {y})"),
Message::Write(s) => println!("Write called with: {s}"),
}
}
}
let m = Message::Write(String::from("hello"));
m.call();
}
1.5 Option 枚举 - 最重要的枚举
Option 是 Rust 标准库中定义的一个枚举,用于表示一个值要么存在(Some),要么不存在(None)。这是 Rust 处理空值的方式,相比于其他语言的 null,更安全。
fn main() {
// Option<T> 的定义(简化版)
// enum Option<T> {
// None,
// Some(T),
// }
// 整数类型的选项
let some_number: Option<i32> = Some(5);
let some_string: Option<&str> = Some("a string");
let absent_number: Option<i32> = None;
// 使用 match 处理 Option
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("six: {:?}", six); // Some(6)
println!("none: {:?}", none); // None
}
1.6 Option 的常用方法
fn main() {
let x: Option<i32> = Some(2);
// is_some() 和 is_none() 检查值是否存在
println!("x is some: {}", x.is_some());
println!("x is none: {}", x.is_none());
// unwrap_or() 如果是 Some 则返回值,否则返回默认值
let y: Option<i32> = None;
println!("y unwrap_or: {}", y.unwrap_or(0));
// unwrap() 取出 Some 中的值,None 会 panic
// println!("x unwrap: {}", x.unwrap());
// map() 对 Some 中的值应用函数
let doubled = x.map(|v| v * 2);
println!("doubled: {:?}", doubled);
// match 完整处理
match x {
Some(val) => println!("Got value: {val}"),
None => println!("Got nothing"),
}
}
1.7 枚举与模式匹配结合
fn main() {
enum Color {
Red,
Green,
Blue,
RGB(u8, u8, u8),
HSL { h: u8, s: u8, l: u8 },
}
let c = Color::RGB(255, 0, 0);
match c {
Color::Red => println!("红色"),
Color::Green => println!("绿色"),
Color::Blue => println!("蓝色"),
// 绑定变量到模式
Color::RGB(r, g, b) => println!("RGB: ({r}, {g}, {b})"),
Color::HSL { h, s, l } => println!("HSL: ({h}, {s}, {l})"),
}
}
2. match 控制流 (The match Control Flow Construct)
match 是 Rust 中强大的模式匹配工具,类似于其他语言的 switch,但更强大。
2.1 基本 match 语法
fn main() {
let coin = Coin::Penny;
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
};
println!("硬币价值: {} 分", value_in_cents(coin));
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
}
2.2 绑定值的模式匹配
匹配时可以将变体关联的值绑定到变量。
fn main() {
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// ... 其他州
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState), // 25美分硬币关联州信息
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
// 绑定 Quarter 变体中的值到 state 变量
Coin::Quarter(state) => {
println!("来自 {:?} 的 quarter!", state);
25
}
}
}
let coin = Coin::Quarter(UsState::Alaska);
println!("价值: {} 分", value_in_cents(coin));
}
2.3 匹配 Option
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
// 绑定 Some 中的值
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("six: {:?}", six); // Some(6)
println!("none: {:?}", none); // None
}
2.4 通配符模式 _
_ 匹配所有值,但不绑定。常用于处理剩余情况。
fn main() {
let some_u8_value = 3u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
// _ 匹配所有其他情况
_ => println!("其他值: {some_u8_value}"),
}
}
2.5 多重模式匹配
使用 | 在同一个分支匹配多个值。
fn main() {
let x = 1;
match x {
1 | 2 => println!("x 是 1 或 2"),
3 => println!("x 是 3"),
_ => println!("x 是其他值"),
}
}
2.6 范围匹配
使用 ..= 匹配一个闭区间范围。
fn main() {
let x = 5;
match x {
1..=5 => println!("x 在 1-5 范围内"),
_ => println!("x 不在 1-5 范围内"),
}
let c = 'c';
match c {
'a'..='z' => println!("小写字母"),
'A'..='Z' => println!("大写字母"),
'0'..='9' => println!("数字"),
_ => println!("其他字符"),
}
}
2.7 解构元组和枚举
fn main() {
// 解构元组
let pair = (1, 2);
match pair {
(x, y) if x == y => println!("相等: {x} = {y}"),
(x, y) if x + y == 3 => println!("和为3: {x} + {y}"),
_ => println!("其他: ({}, {})", pair.0, pair.1),
}
// 解构枚举
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
// 解构结构体枚举
Message::Hello { id } if id >= 5 && id < 10 => {
println!("id 在 5-9 之间: {id}")
}
Message::Hello { id } => {
println!("id 是: {id}")
}
}
}
2.8 match 必须穷尽所有情况
fn main() {
let dice_roll = 9;
match dice_roll {
3 => println!("你得到一个惊喜!"),
7 => println!("你倒霉了!"),
// 其他情况不做任何事
_ => (), // () 是单元值,表示什么都不做
}
}
2.9 match 绑定守卫 (Match Guard)
使用 if 条件添加额外的过滤条件。
fn main() {
let num = Some(4);
match num {
// 只有当值是 Some 且内部值 > 2 时才匹配
Some(x) if x > 2 => println!("大于2: {x}"),
Some(x) => println!("小于等于2: {x}"),
None => println!("没有值"),
}
// 结合多重模式
let x = 4;
let y = false;
match (x, y) {
// 只有 (4, true) 或 (5, true) 匹配
(4, true) | (5, true) => println!("x 是 4 或 5,且 y 是 true"),
_ => println!("其他情况"),
}
}
2.10 @ 绑定 (Binding)
@ 允许在匹配时同时绑定值到一个变量并测试它。
fn main() {
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
// 绑定 id 值到 variable,同时测试范围
Message::Hello {
id: variable @ 3..=7,
} => println!("id 在 3-7 范围内: {variable}"),
Message::Hello { id } => println!("id 是: {id}"),
}
}
3. if let 简洁控制流 (Concise Control Flow with if let and let...else)
当只关心某一个匹配而忽略其他情况时,match 可能显得冗长。if let 提供了更简洁的语法。
3.1 if let 基本语法
fn main() {
// 只关心 Some(3) 的情况
let some_value = Some(3);
// 传统的 match 写法
match some_value {
Some(3) => println!("是 Three!"),
_ => (),
}
// 使用 if let 更简洁
if let Some(3) = some_value {
println!("是 Three!");
}
}
3.2 if let 处理枚举
fn main() {
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
let coin = Coin::Penny;
// 传统 match
match coin {
Coin::Quarter => println!("25美分!"),
_ => println!("不是25美分"),
}
// 使用 if let
if let Coin::Quarter = coin {
println!("是25美分硬币!");
} else {
println!("不是25美分硬币");
}
}
3.3 if let 与 Option
fn main() {
let some_u8_value = Some(3u8);
// 传统 match
match some_u8_value {
Some(3) => println!("three"),
_ => println!("不是3"),
}
// if let 更简洁
if let Some(3) = some_u8_value {
println!("是 three");
}
}
3.4 if let...else 结构
fn main() {
let some_value = Some(3);
// if let...else 结构
if let Some(value) = some_value {
println!("有值: {value}");
} else {
println!("没有值");
}
// 对比 match
match some_value {
Some(value) => println!("有值: {value}"),
None => println!("没有值"),
}
}
3.5 matches! 宏
matches! 宏用于测试一个值是否匹配某个模式,返回布尔值。
fn main() {
let value = Some(3u8);
// matches! 宏
println!("matches Some(3): {}", matches!(value, Some(3)));
println!("matches Some(_): {}", matches!(value, Some(_)));
println!("matches None: {}", matches!(value, None));
// 配合范围使用
let x = 5;
println!("x matches 1..=10: {}", matches!(x, 1..=10));
// 枚举变体匹配
enum MyEnum {
Foo,
Bar,
Baz,
}
let e = MyEnum::Foo;
println!("e matches Foo or Bar: {}", matches!(e, MyEnum::Foo | MyEnum::Bar));
}
3.6 matches! 用于条件判断
fn main() {
#[derive(Debug)]
enum WebEvent {
PageLoad,
PageUnload,
KeyPress(char),
Click { x: i64, y: i64 },
}
fn inspect(event: &WebEvent) {
// 使用 matches! 配合 if
if matches!(event, WebEvent::PageLoad | WebEvent::PageUnload) {
println!("页面加载或卸载事件");
} else if matches!(event, WebEvent::KeyPress(c) if c == 'a') {
println!("按下了 'a' 键");
} else if matches!(event, WebEvent::Click { x, y } if x > 0 && y > 0) {
println!("点击了正坐标区域");
}
}
inspect(&WebEvent::PageLoad);
inspect(&WebEvent::KeyPress('a'));
inspect(&WebEvent::Click { x: 10, y: 10 });
}
3.7 let...else 模式
let...else 是一种模式匹配构造,当模式匹配失败时提前返回。
fn main() {
// 如果匹配失败,else 分支必须返回或发散(panic/loop)
let Some(value) = Some(3) else {
return;
};
println!("value: {value}");
// 更完整的例子
fn get_color_name(color: u8) -> &'static str {
// 匹配失败时返回 "unknown"
let Some(name) = match color {
0 => Some("red"),
1 => Some("green"),
2 => Some("blue"),
_ => None,
} else {
"unknown"
};
name
}
println!("color 0: {}", get_color_name(0));
println!("color 99: {}", get_color_name(99));
}
3.8 let...else 与错误处理
fn main() {
// 典型的错误处理模式
fn parse_number(s: &str) -> Option<i32> {
let Ok(num) = s.parse::<i32>() else {
return None;
};
Some(num)
}
println!("parse '42': {:?}", parse_number("42"));
println!("parse 'abc': {:?}", parse_number("abc"));
// 更复杂的例子
enum Temperature {
Celsius(i32),
Fahrenheit(i32),
}
fn get_celsius(temp: Temperature) -> Option<i32> {
let Temperature::Fahrenheit(f) = temp else {
// 如果不是华氏度,直接返回 Some(已经是摄氏度)
return None;
};
Some((f - 32) * 5 / 9)
}
let c = Temperature::Celsius(30);
let f = Temperature::Fahrenheit(86);
println!("Celsius 30 -> {:?}", get_celsius(c));
println!("Fahrenheit 86 -> {:?}", get_celsius(f));
}
3.9 while let 条件循环
while let 在条件为真时循环。
fn main() {
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
// while let 循环弹出栈中的元素
while let Some(top) = stack.pop() {
println!("弹出: {top}");
}
// 栈为空后循环结束
println!("栈为空: {:?}", stack);
}
3.10 for 循环中的模式匹配
fn main() {
let v = vec![1, 2, 3];
// for 循环解构元组
for (index, value) in v.iter().enumerate() {
println!("索引 {index} -> 值 {value}");
}
// 解构元组
let pairs = [(1, "one"), (2, "two"), (3, "three")];
for (num, name) in pairs {
println!("{num} -> {name}");
}
}
综合练习
fn main() {
// 综合示例:IP地址处理
enum IpAddrKind {
V4,
V6,
}
enum IpAddr {
V4(String),
V6(String),
}
// 也可以更复杂
enum IpAddr2 {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
// 处理不同类型的 IP
fn route(ip_kind: IpAddrKind, ip: &IpAddr) {
match ip {
IpAddr::V4(addr) => println!("IPv4 地址: {addr}"),
IpAddr::V6(addr) => println!("IPv6 地址: {addr}"),
}
}
route(IpAddrKind::V4, &home);
route(IpAddrKind::V6, &loopback);
// Option<T> 的实用示例:获取数组元素
let numbers = [1, 2, 3, 4, 5];
fn get_element(arr: &[i32], index: usize) -> Option<i32> {
// 使用索引访问数组
if index < arr.len() {
Some(arr[index])
} else {
None
}
}
println!("元素 2: {:?}", get_element(&numbers, 2));
println!("元素 10: {:?}", get_element(&numbers, 10));
// 使用 if let 处理
if let Some(val) = get_element(&numbers, 2) {
println!("找到了元素: {val}");
}
// 使用 matches! 过滤
let results = [Ok(1), Err("error"), Ok(3), Ok(4)];
let successful: Vec<_> = results
.iter()
.filter(|r| matches!(r, Ok(_)))
.collect();
println!("成功的结果: {:?}", successful);
// 使用for循环遍历数字1到4
for i in 1..5 {
println!("当前数字: {}", i);
}
// 使用for循环遍历数字1到5
for i in 1..=5 {
println!("当前数字: {}", i);
}
// 使用for循环遍历一个数组
let nums = vec![1, 2, 3, 4, 5];
for num in &nums {
// 借用数组元素进行匹配
match num {
1 => println!("数字是1 "),
2 => println!("数字是2 "),
3 => println!("数字是3 "),
_ => println!("数字是{}", num),
}
}
// 使用for循环遍历一个数组
let nums = vec![1, 2, 3, 4, 5];
for num in &nums {
// 借用数组元素进行匹配
match num {
1 => println!("数字是1 "),
2 => println!("数字是2 "),
3 => println!("数字是3 "),
_ => println!("数字是{}", num),
}
}
// 使用for循环遍历一个数组,直接获取借用权
let nums = vec![1, 2, 3, 4, 5];
for num in nums.iter() {
// 借用数组元素进行匹配
match num {
1 => println!("数字是1 "),
2 => println!("数字是2 "),
3 => println!("数字是3 "),
_ => println!("数字是{}", num),
}
}
// 使用into_iter()消耗数组
let nums = vec![1, 2, 3, 4, 5];
for num in nums.into_iter() {
// 借用数组元素进行匹配
match num {
1 => println!("数字是1 "),
2 => println!("数字是2 "),
3 => println!("数字是3 "),
_ => println!("数字是{}", num),
}
}
// 这行会导致编译错误,因为nums已经被消耗了
// println!("nums数组已被消耗,无法再使用: {:?}", nums);
let mut nums = vec![1, 2, 3, 4, 5];
for num in nums.iter_mut() {
// 修改数组元素
*num *= 2; // 将每个元素乘以2
}
println!("修改后的数组: {:?}", nums);
}
小结
- 枚举定义:枚举用于定义有限的类型变体,每个变体可以关联不同的数据
- Option
:Rust 处理空值的标准方式, Some(T)表示有值,None表示无值 - match:强大的模式匹配工具,必须穷尽所有可能的情况
- 绑定值:模式匹配时可以绑定变体关联的值
- _ 通配符:匹配所有其他情况
- if let:简化只关心一个匹配的场景
- matches! 宏:返回布尔值的模式测试
- let...else:模式匹配失败时提前返回
方法语法
定义方法
方法是关联到某个结构体(或枚举、trait 对象)的函数。与普通函数不同的是,方法需要在 impl 块中定义,并且第一个参数永远是 self(或 &self、&mut self)。
struct Rectangle {
width: u32,
height: u32,
}
// 为 Rectangle 实现方法
impl Rectangle {
// 方法:计算面积
fn area(&self) -> u32 {
self.width * self.height
}
// 方法:检查宽度是否大于高度
fn width_greater_than_height(&self) -> bool {
self.width > self.height
}
// 方法:调整尺寸(需要可变引用)
fn resize(&mut self, new_width: u32, new_height: u32) {
self.width = new_width;
self.height = new_height;
}
}
fn main() {
let mut rect = Rectangle {
width: 30,
height: 50,
};
// 调用方法
println!("面积: {}", rect.area());
println!("宽度大于高度: {}", rect.width_greater_than_height());
// 调用修改自身的方法
rect.resize(40, 30);
println!("调整后宽度大于高度: {}", rect.width_greater_than_height());
println!("新面积: {}", rect.area());
}
自动引用和解引用
在 Rust 中,调用方法时会自动进行引用和解引用。当使用 object.method() 时,Rust 会自动添加 &、&mut 或 *,以确保方法可以正确访问对象。
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn width(&self) -> u32 {
self.width
}
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
// 以下两种调用方式效果相同
let w1 = rect.width(); // 自动添加 &self
let w2 = (&rect).width(); // 手动添加 &self
println!("w1 = {}, w2 = {}", w1, w2);
// 类似地,对于可变方法
let mut rect2 = Rectangle {
width: 10,
height: 20,
};
// 两种方式等价
rect2.resize(20, 40);
(&mut rect2).resize(20, 40);
}
Rust 的自动引用设计使得方法调用更加简洁,无需手动处理引用和解引用。
关联函数
关联函数是在 impl 块中定义但不以 self 作为参数的函数。关联函数通常用作构造器,返回结构体的新实例。
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 关联函数:创建正方形
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
// 关联函数:创建指定宽高的矩形
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
// 方法:获取面积
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
// 使用关联函数创建实例
let rect = Rectangle::new(30, 50);
let square = Rectangle::square(25);
println!("矩形: {}x{}, 面积: {}", rect.width, rect.height, rect.area());
println!("正方形: {}x{}, 面积: {}", square.width, square.height, square.area());
}
多个 impl 块
可以为同一个结构体定义多个 impl 块,这在实现 trait 时特别有用。
struct Rectangle {
width: u32,
height: u32,
}
// 第一部分 impl 块:定义构造函数
impl Rectangle {
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
}
// 第二部分 impl 块:定义只读方法
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn width(&self) -> u32 {
self.width
}
fn height(&self) -> u32 {
self.height
}
}
// 第三部分 impl 块:定义可变方法
impl Rectangle {
fn resize(&mut self, new_width: u32, new_height: u32) {
self.width = new_width;
self.height = new_height;
}
fn set_width(&mut self, width: u32) {
self.width = width;
}
}
fn main() {
let mut rect = Rectangle::new(30, 50);
println!("宽: {}, 高: {}, 面积: {}", rect.width(), rect.height(), rect.area());
rect.resize(40, 60);
println!("调整后 - 宽: {}, 高: {}, 面积: {}", rect.width(), rect.height(), rect.area());
rect.set_width(35);
println!("设置宽后 - 宽: {}, 面积: {}", rect.width(), rect.area());
}
多个 impl 块不会影响程序性能,编译器会将其合并处理。这在实现 trait 时尤其有用,可以将 trait 方法和自有方法分开到不同的 impl 块中。
方法与函数的区别
| 特性 | 方法 | 函数 |
|---|---|---|
| 定义位置 | impl 块内 |
函数外面 |
| 第一个参数 | self/&self/&mut self |
自定义参数 |
| 调用方式 | object.method() |
function(args) |
| 关联函数 | 不带 self 的函数 |
普通函数 |
struct Counter {
count: u32,
}
impl Counter {
// 关联函数:创建计数器
fn new() -> Counter {
Counter { count: 0 }
}
// 方法:增加计数
fn increment(&mut self) {
self.count += 1;
}
// 方法:获取当前计数
fn value(&self) -> u32 {
self.count
}
}
fn main() {
let mut counter = Counter::new(); // 关联函数调用
counter.increment(); // 方法调用
counter.increment();
println!("计数: {}", counter.value()); // 输出: 计数: 2
}
结构体方法设计建议
- 方法应该访问
self:如果函数需要访问结构体的字段,将其定义为方法 - 使用
&self:如果方法只读取数据,使用不可变引用 - 使用
&mut self:如果方法需要修改数据,使用可变引用 - 使用关联函数创建实例:将构造逻辑放在关联函数中(如
new()) - 保持方法简洁:每个方法只做一件事
8. 常见集合 (Common Collections)
Rust 标准库提供了一系列常用的集合类型,它们都是使用泛型实现的。本章介绍三种最常用的集合:Vec<T>(动态数组)、String(字符串)和 HashMap<K, V>(哈希映射)。
8.1 Vec - 动态数组
Vec<T> 是一个可以存储任意数量相同类型元素的集合。它会在堆上分配内存。
创建 Vec
fn main() {
// 使用 Vec::new() 创建空向量
let mut v1: Vec<i32> = Vec::new();
v1.push(1);
v1.push(2);
println!("v1: {:?}", v1); // v1: [1, 2]
// 使用 vec! 宏创建并初始化向量
let v2 = vec![1, 2, 3, 4, 5];
println!("v2: {:?}", v2); // v2: [1, 2, 3, 4, 5]
// 指定容量创建 Vec
let mut v3: Vec<i32> = Vec::with_capacity(10);
println!("容量: {}, 长度: {}", v3.capacity(), v3.len()); // 容量: 10, 长度: 0
v3.push(1);
v3.push(2);
println!("添加后 - 容量: {}, 长度: {}", v3.capacity(), v3.len()); // 容量: 10, 长度: 2
}
添加和移除元素
fn main() {
let mut v = Vec::new();
v.push(1); // 添加元素到末尾
v.push(2);
v.push(3);
println!("push 后: {:?}", v); // push 后: [1, 2, 3]
let last = v.pop(); // 移除并返回最后一个元素
println!("pop: {:?}, 剩余: {:?}", last, v); // pop: Some(3), 剩余: [1, 2]
let second = v.remove(1); // 移除指定索引的元素
println!("remove(1): {}, 剩余: {:?}", second, v); // remove(1): 2, 剩余: [1]
}
访问元素
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 使用索引访问(越界会 panic)
println!("v[2] = {}", v[2]); // v[2] = 3
// 使用 get 方法访问(返回 Option)
match v.get(2) {
Some(val) => println!("v.get(2) = {}", val), // v.get(2) = 3
None => println!("索引越界"),
}
// 安全访问不存在的索引
match v.get(100) {
Some(val) => println!("值: {}", val),
None => println!("v.get(100) 返回 None"), // v.get(100) 返回 None
}
// 安全的索引访问方式
if let Some(val) = v.get(10) {
println!("值: {}", val);
} else {
println!("索引 10 不存在,长度为 {}", v.len()); // 索引 10 不存在,长度为 5
}
}
遍历 Vec
fn main() {
let v = vec![1, 2, 3];
// 不可变引用遍历
println!("不可变遍历:");
for i in &v {
println!(" 元素: {}", i);
}
// 可变引用遍历
let mut v2 = vec![1, 2, 3];
println!("可变遍历并修改:");
for i in &mut v2 {
*i *= 2;
println!(" 元素: {}", i);
}
println!("v2 修改后: {:?}", v2); // v2 修改后: [2, 4, 6]
}
Vec 的常用方法
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
// 检查是否为空
println!("是否为空: {}", v.is_empty()); // 是否为空: false
// 获取长度
println!("长度: {}", v.len()); // 长度: 5
// 清空
v.clear();
println!("清空后长度: {}", v.len()); // 清空后长度: 0
// 重新填充
v.extend(1..=5);
println!("extend 后: {:?}", v); // extend 后: [1, 2, 3, 4, 5]
// 包含检查
println!("包含 3: {}", v.contains(&3)); // 包含 3: true
// 转换为数组切片
let slice = &v[1..4];
println!("切片 [1..4]: {:?}", slice); // 切片 [1..4]: [2, 3, 4]
}
8.2 String - 字符串
Rust 的字符串是一个基于 UTF-8 编码的字节序列。相比其他语言,Rust 的字符串处理更加安全。
创建 String
fn main() {
// 创建空字符串
let s1 = String::new();
println!("空字符串: '{}'", s1); // 空字符串: ''
// 从字面量创建
let s2 = String::from("hello");
let s3 = "world".to_string();
println!("s2: {}, s3: {}", s2, s3); // s2: hello, s3: world
// 创建重复字符的字符串
let s4 = "ABC".repeat(3);
println!("repeat: {}", s4); // repeat: ABCABCABC
// 从字符串切片创建(需要 &str)
let s5: String = "hello".into();
println!("from str: {}", s5); // from str: hello
}
修改 String
fn main() {
let mut s = String::from("hello");
// push 单个字符
s.push(' ');
s.push('w');
s.push('o');
s.push('r');
s.push('l');
s.push('d');
println!("push 后: {}", s); // push 后: hello world
// push_str 附加字符串切片
let s2 = "!!!!";
s.push_str(s2);
println!("push_str 后: {}", s); // push_str 后: hello world!!!!
// 使用 + 拼接(会转移所有权)
let s3 = String::from("Hello, ");
let s4 = String::from("world!");
let s5 = s3 + &s4; // s3 被移动,不能再使用
println!("+ 拼接: {}", s5); // + 拼接: Hello, world!
// 使用 format! 拼接多个字符串
let s6 = String::from("tic");
let s7 = String::from("tac");
let s8 = String::from("toe");
let s9 = format!("{}-{}-{}", s6, s7, s8);
println!("format!: {}", s9); // format!: tic-tac-toe
}
String 的常用操作
fn main() {
let s = String::from("hello world");
// 获取长度(字节数,不是字符数)
println!("字节长度: {}", s.len()); // 字节长度: 11
// 检查是否为空
println!("是否为空: {}", s.is_empty()); // 是否为空: false
// 截取子串
let sub = &s[0..5]; // 注意:索引基于字节
println!("子串 [0..5]: {}", sub); // 子串 [0..5]: hello
// 替换
let s2 = s.replace("world", "rust");
println!("replace: {}", s2); // replace: hello rust
// 转换为字节数组
println!("字节数组: {:?}", s.as_bytes()); // 字节数组: [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
// 遍历字符
println!("字符遍历:");
for c in s.chars() {
print!("{} ", c);
}
println!(); // h e l l o w o r l d
// 遍历字节
println!("字节遍历:");
for b in s.bytes() {
print!("{} ", b);
}
println!(); // 104 101 108 108 111 32 119 111 114 108 100
}
字符串的其他常用方法
fn main() {
let s = String::from(" Hello, World! ");
// trim 去除两端空白
println!("trim: '{}'", s.trim()); // trim: 'Hello, World!'
// to_lowercase / to_uppercase
println!("大写: {}", s.trim().to_uppercase()); // 大写: HELLO, WORLD!
println!("小写: {}", "Rust".to_lowercase()); // 小写: rust
// split / split_whitespace
let s2 = "apple,banana,cherry";
let parts: Vec<&str> = s2.split(',').collect();
println!("split: {:?}", parts); // split: ["apple", "banana", "cherry"]
let s3 = "hello world\t\nrust";
let words: Vec<&str> = s3.split_whitespace().collect();
println!("split_whitespace: {:?}", words); // split_whitespace: ["hello", "world", "rust"]
// starts_with / ends_with
println!("starts with 'hello': {}", s3.starts_with("hello")); // starts with 'hello': true
println!("ends with 'rust': {}", s3.ends_with("rust")); // ends with 'rust': true
// contains
println!("contains 'world': {}", s3.contains("world")); // contains 'world': true
}
UTF-8 注意事项
fn main() {
// 中文字符在 UTF-8 中占用多个字节
let chinese = "你好";
println!("中文长度(字节): {}", chinese.len()); // 中文长度(字节): 6
// 使用 chars().count() 获取字符数
println!("中文字符数: {}", chinese.chars().count()); // 中文字符数: 2
// 遍历字符
println!("字符遍历:");
for c in chinese.chars() {
print!("{} ", c);
}
println!(); // 你 好
// 中英文混合
let mixed = "Hello 你好";
println!("混合字符串字节长度: {}", mixed.len()); // 混合字符串字节长度: 12
println!("混合字符串字符数: {}", mixed.chars().count()); // 混合字符串字符数: 8
}
8.3 HashMap - 哈希映射
HashMap 存储键值对,键必须是可以哈希的类型(实现了 Eq 和 Hash trait)。
创建 HashMap
fn main() {
use std::collections::HashMap;
// 使用 new 创建空的 HashMap
let mut scores: HashMap<String, i32> = HashMap::new();
// 使用 insert 插入键值对
scores.insert(String::from("Alice"), 100);
scores.insert(String::from("Bob"), 85);
scores.insert(String::from("Charlie"), 92);
println!("scores: {:?}", scores);
// scores: {"Charlie": 92, "Bob": 85, "Alice": 100}
// 使用 from 创建 HashMap
let teams = vec![String::from("Red"), String::from("Blue")];
let initial_scores = vec![10, 50];
let scores2: HashMap<_, _> = teams.into_iter().zip(initial_scores.into_iter()).collect();
println!("teams scores: {:?}", scores2);
// teams scores: {"Red": 10, "Blue": 50}
}
访问和修改 HashMap
fn main() {
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Alice"), 100);
scores.insert(String::from("Bob"), 85);
// get 获取值(返回 Option<&V>)
match scores.get(&String::from("Alice")) {
Some(score) => println!("Alice 的分数: {}", score), // Alice 的分数: 100
None => println!("未找到"),
}
// 遍历键值对
println!("所有分数:");
for (name, score) in &scores {
println!(" {}: {}", name, score);
}
// 更新值
scores.insert(String::from("Alice"), 105); // 覆盖旧值
println!("更新后 Alice: {:?}", scores.get(&String::from("Alice"))); // Some(105)
// or_insert:当键不存在时插入默认值
let entry = scores.entry(String::from("Charlie")).or_insert(0);
*entry += 10; // 如果 Charlie 不存在,设为 0,然后加 10
println!("Charlie 的分数: {:?}", scores.get(&String::from("Charlie"))); // Some(10)
}
HashMap 的常用操作
fn main() {
use std::collections::HashMap;
let mut map: HashMap<&str, i32> = HashMap::new();
map.insert("a", 1);
map.insert("b", 2);
map.insert("c", 3);
// 检查是否包含键
println!("包含 'a': {}", map.contains_key("a")); // 包含 'a': true
// 获取键的数量
println!("键数量: {}", map.len()); // 键数量: 3
// 检查是否为空
println!("是否为空: {}", map.is_empty()); // 是否为空: false
// 移除键值对
let removed = map.remove("b");
println!("移除 'b': {:?}", removed); // 移除 'b': Some(2)
println!("剩余: {:?}", map); // 剩余: {"a": 1, "c": 3}
// 清空
map.clear();
println!("清空后: {:?}", map); // 清空后: {}
}
HashMap 的 entry API
entry API 用于更灵活地处理键存在与否的情况。
fn main() {
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut word_count = HashMap::new();
// 使用 entry 统计单词出现次数
for word in text.split_whitespace() {
// or_insert 返回值的可变引用
let count = word_count.entry(word).or_insert(0);
*count += 1;
}
println!("词频统计: {:?}", word_count);
// 词频统计: {"hello": 1, "world": 2, "wonderful": 1}
// 使用 entry 检查并更新
let mut scores = HashMap::new();
scores.insert("Red", 10);
// 如果不存在,设置默认值
let entry = scores.entry(String::from("Blue")).or_insert(50);
println!("Blue 默认分数: {}", entry); // Blue 默认分数: 50
// 如果存在,使用 existing 值
let entry2 = scores.entry(String::from("Red")).or_insert(50);
*entry2 += 5;
println!("Red 更新后分数: {}", scores[&"Red"]); // Red 更新后分数: 15
}
// Vector
// 1. Vector 是一个动态数组,可以在运行时改变大小。它在堆上分配内存,包含一个指向堆数据的指针、长度和容量。Vector 可以存储任意类型的数据,并且提供了许多方法来操作数据。
// 2. Vector 的元素类型必须是相同的,但 Vector 本身可以存储不同类型的数据(通过使用枚举或 trait 对象)。
// 3. Vector 提供了许多方法来操作数据,如 push、pop、insert、remove 等。Vector 还实现了许多 trait,如 Deref、Index、IntoIterator 等,使得它在使用上非常灵活。
// HashMap
// 1. HashMap 是一个键值对集合,提供了快速的查找
// 2. HashMap 在堆上分配内存,包含一个指向堆数据的指针、长度和容量。HashMap 的键和值可以是任意类型,但键必须实现 Hash 和 Eq trait。
// 3. HashMap 提供了许多方法来操作数据,如 insert、get、remove 等。HashMap 还实现了许多 trait,如 Deref、Index、IntoIterator 等,使得它在使用上非常灵活。
// HashSet
// 1. HashSet 是一个集合,存储唯一的值。它在堆上分配内存,包含一个指向堆数据的指针、长度
// 2. HashSet 的元素类型必须是相同的,但 HashSet 本身可以存储不同类型的数据(通过使用枚举或 trait 对象)。HashSet 的元素必须实现 Hash 和 Eq trait。
// 3. HashSet 提供了许多方法来操作数据,如 insert、contains、remove 等。HashSet 还实现了许多 trait,如 Deref、Index、IntoIterator 等,使得它在使用上非常灵活。
fn main(){
// 定义一个可变的Vector
let mut vector = Vec::new();
vector.push(1);
vector.push(2);
vector.push(3);
// 使用for循环遍历Vector
for num in &vector {
println!("当前元素: {}", num);
}
println!("vector: {:?}", vector);
vector.pop(); // 移除并返回最后一个元素
println!("vector after pop: {:?}", vector);
// remove方法根据索引移除元素
vector.remove(0); // 移除索引为0的元素
println!("vector after remove: {:?}", vector);
// contains方法检查Vector是否包含某个元素
println!("vector contains 2: {}", vector.contains(&2));
// index方法根据索引访问元素
println!("vector length: {}", vector[0]);
// clear方法清空Vector
vector.clear();
println!("vector after clear: {:?}", vector);
let mut hashmap = std::collections::HashMap::new();
hashmap.insert("key1", "value1");
hashmap.insert("key2", "value2");
hashmap.insert("key3", "value3");
for (key, value) in &hashmap {
println!("{}: {}", key, value);
}
hashmap.remove("key2"); // 移除键为"key2"的键值对
println!("hashmap after remove: {:?}", hashmap);
// contains_key方法检查HashMap是否包含某个键
println!("hashmap contains key1: {}", hashmap.contains_key("key1"));
// get方法根据键访问值
println!("value for key1: {:?}", hashmap.get("key1"));
// clear方法清空HashMap
hashmap.clear();
println!("hashmap after clear: {:?}", hashmap);
println!("-------------------");
let mut hashset = std::collections::HashSet::new();
hashset.insert(1);
hashset.insert(2);
hashset.insert(3);
for num in &hashset {
println!("当前元素: {}", num);
}
for num in hashset.iter() {
println!("当前元素: {}", num);
}
hashset.remove(&2); // 移除元素2
println!("hashset after remove: {:?}", hashset);
// contains方法检查HashSet是否包含某个元素
println!("hashset contains 1: {}", hashset.contains(&1));
match hashset.get(&1) {
Some(value) => println!("value for 1: {}", value),
None => println!("1 is not in the hashset"),
}
// clear方法清空HashSet
hashset.clear();
println!("hashset after clear: {:?}", hashset);
hashset.remove(&1); // 移除元素1,虽然hashset已经被清空了,但remove方法不会报错
println!("hashset after remove 1: {:?}", hashset);
}
集合类型的选择建议
| 集合 | 使用场景 |
|---|---|
Vec<T> |
需要存储同类型元素的序列,需要随机访问 |
String |
处理文本数据,需要 UTF-8 编码的字符串 |
HashMap<K, V> |
需要键值对映射,快速查找键对应的值 |
总结
Rust 的集合类型都实现了所有权和借用规则:
Vec<T>存储具体类型的值String是 UTF-8 编码的字符串HashMap<K, V>通过哈希函数实现高效的键值查找
这些集合都分配在堆上,可以根据需要动态增长和缩小,是 Rust 编程中最常用的数据结构。
Chapter 9 错误处理
Rust 的错误处理分为两大类:不可恢复错误(panic!)和可恢复错误(Result)。
9.1 panic! 与不可恢复错误
panic! 用于处理那些无法恢复的错误,比如遇到了严重的 bug 导致程序无法继续运行。
触发 panic 的场景
fn main() {
// 1. 直接调用 panic! 宏
// panic!("程序遇到严重错误,无法继续");
// 2. 数组越界访问(debug 模式下会 panic,release 模式下会做边界检查)
let arr = [1, 2, 3];
// println!("{}", arr[10]); // 这会 panic
// 3. 除以零
// let result = 10 / 0; // 整数除法会 panic
}
panic! vs unwrap
在生产代码中,应该避免使用 unwrap 和 expect,因为它们在遇到错误时会 panic。
fn main() {
// 不推荐:在生产代码中使用 unwrap
// let result = some_function().unwrap(); // 如果出错会 panic
// 推荐:使用 expect 提供更有意义的错误信息
// let result = some_function().expect("应该能够获取到值");
// 更推荐:使用 ? 操作符或 match 处理错误
}
panic! 的堆栈回溯
当 panic 发生时,Rust 会打印堆栈回溯信息,帮助定位问题。
fn main() {
// 在 debug 模式下运行会显示完整的堆栈回溯
let v = vec![1, 2, 3];
v[99]; // panic: index out of bounds
}
9.2 Result 枚举与可恢复错误
Result 是 Rust 用来处理可恢复错误的枚举类型。
enum Result<T, E> {
Ok(T), // 成功时包含值
Err(E), // 失败时包含错误信息
}
基本用法
use std::fs::File;
use std::io::Read;
fn main() {
// 尝试打开文件,可能失败
let mut file = match File::open("hello.txt") {
Ok(file) => file,
Err(error) => {
println!("无法打开文件: {:?}", error);
return;
}
};
// 读取文件内容
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => println!("文件内容: {}", contents),
Err(error) => println!("读取文件失败: {:?}", error),
}
}
使用 match 处理错误
use std::net::IpAddr;
fn main() {
// parse 返回 Result
let home: Result<IpAddr, _> = "127.0.0.1".parse();
match home {
Ok(ip) => println!("解析成功: {:?}", ip),
Err(e) => println!("解析失败: {:?}", e),
}
}
unwrap 和 expect
在确定不会失败的情况下使用,适合原型开发和测试。
fn main() {
// unwrap:成功时返回值,失败时 panic
// let value = Some("ok").unwrap();
// expect:类似 unwrap,但可以提供自定义错误信息
// let value = Some("ok").expect("这里应该有值");
// 失败的情况
// let value: Option<&str> = None;
// value.expect("这里应该有值"); // panic: 这里应该有值
}
unwrap_or 和 unwrap_or_default
提供默认值处理失败情况。
fn main() {
let good: Result<i32, &str> = Ok(42);
let bad: Result<i32, &str> = Err("坏消息");
// unwrap_or:失败时返回指定的默认值
println!("good unwrap_or: {}", good.unwrap_or(0)); // 42
println!("bad unwrap_or: {}", bad.unwrap_or(0)); // 0
// unwrap_or_default:失败时返回类型的默认值
println!("bad unwrap_or_default: {}", bad.unwrap_or_default()); // 0
}
9.3 ? 操作符与错误传播
? 操作符是 Rust 中最强大的错误处理方式,可以简化错误传播。
基本用法
use std::fs::File;
use std::io;
use std::io::Read;
fn read_file_contents(path: &str) -> Result<String, io::Error> {
// ? 操作符:如果结果是 Ok,返回其中的值;如果是 Err,立即返回该错误
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn main() {
// 使用 match 处理返回的结果
match read_file_contents("test.txt") {
Ok(contents) => println!("文件内容: {}", contents),
Err(e) => println!("错误: {:?}", e),
}
}
? 与 match 的对比
use std::fs::File;
use std::io::Read;
// 使用 match 的版本(冗长)
fn read_file_match(path: &str) -> Result<String, std::io::Error> {
let mut file = match File::open(path) {
Ok(f) => f,
Err(e) => return Err(e),
};
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => {}
Err(e) => return Err(e),
}
Ok(contents)
}
// 使用 ? 操作符的版本(简洁)
fn read_file_question(path: &str) -> Result<String, std::io::Error> {
let mut file = File::open(path)?; // ? 自动处理错误传播
let mut contents = String::new();
file.read_to_string(&mut contents)?; // ? 自动处理错误传播
Ok(contents)
}
From trait 与错误转换
? 会自动调用 From trait 将低层错误转换为函数返回的错误类型。
use std::io;
use std::fmt;
// 自定义错误类型
#[derive(Debug)]
enum MyError {
Io(io::Error),
Parse(std::num::ParseIntError),
}
impl From<io::Error> for MyError {
fn from(err: io::Error) -> Self {
MyError::Io(err)
}
}
impl From<std::num::ParseIntError> for MyError {
fn from(err: std::num::ParseIntError) -> Self {
MyError::Parse(err)
}
}
fn parse_number(s: &str) -> Result<i32, MyError> {
// ? 会自动将 ParseIntError 转换为 MyError
let num: i32 = s.parse::<i32>()?;
Ok(num)
}
fn main() {
match parse_number("42") {
Ok(n) => println!("解析成功: {}", n),
Err(e) => println!("错误: {:?}", e),
}
}
9.4 自定义错误类型
为应用程序定义专门的错误类型,使错误处理更加清晰。
定义错误类型
use std::fmt;
// 使用 enum 定义多种错误类型
#[derive(Debug)]
enum AppError {
NotFound(String), // 资源未找到
InvalidInput(String), // 无效的输入
IoError(std::io::Error), // IO 错误
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::NotFound(msg) => write!(f, "未找到: {}", msg),
AppError::InvalidInput(msg) => write!(f, "无效输入: {}", msg),
AppError::IoError(e) => write!(f, "IO 错误: {}", e),
}
}
}
impl std::error::Error for AppError {}
// 为 std::io::Error 提供转换
impl From<std::io::Error> for AppError {
fn from(err: std::io::Error) -> Self {
AppError::IoError(err)
}
}
使用自定义错误类型
use std::fs::File;
use std::io::Read;
fn read_config(path: &str) -> Result<String, AppError> {
// 读取文件,错误会自动转换
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn main() {
match read_config("config.toml") {
Ok(content) => println!("配置内容:\n{}", content),
Err(e) => {
eprintln!("错误: {}", e);
std::process::exit(1);
}
}
}
9.5 何时 panic,何时返回 Result
选择合适的错误处理策略是编写健壮程序的关键。
应该 panic 的情况
- 不可能发生的情况:使用 unwrap() 表明某个情况在逻辑上不可能发生。
fn main() {
// 永远不会是 None,因为 Some(42) 明显存在
let value = Some(42).unwrap();
println!("值: {}", value);
}
- 原型开发:快速编写代码,验证逻辑。
fn main() {
// 原型阶段使用 unwrap 快速测试
let config = std::env::var("CONFIG_PATH").unwrap();
}
- 测试代码:panic 是测试失败的一种形式。
#[cfg(test)]
mod tests {
#[test]
fn test_add() {
assert_eq!(2 + 2, 4);
}
}
- 示例代码:展示概念,假设输入总是有效。
fn main() {
// 示例中假设用户会输入数字
let input = "42";
let number: i32 = input.parse().unwrap();
println!("数字: {}", number);
}
应该返回 Result 的情况
- 可能失败的操作:文件、网络、用户输入等。
use std::fs::File;
fn read_file(path: &str) -> Result<String, std::io::Error> {
// 文件可能不存在,权限可能不够
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
- 需要调用者决策:错误处理方式取决于调用者。
fn parse_integer(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse::<i32>()
}
- 库代码:将错误信息传递给调用者。
// 库函数应该返回 Result,让使用者决定如何处理
pub fn do_something_risky() -> Result<Data, Error> {
// ...
}
实用建议
| 场景 | 推荐方式 |
|---|---|
| 原型快速验证 | unwrap / expect |
| 测试断言 | assert! / panic! |
| 库代码 | 返回 Result |
| 应用代码 | 根据情况选择 |
| 确定性不可能的情况 | unwrap(附带注释说明) |
9.6 综合示例:带错误处理的文件处理
use std::fs;
use std::io;
use std::path::Path;
#[derive(Debug)]
enum FileError {
NotFound(String),
PermissionDenied(String),
Io(io::Error),
}
impl From<io::Error> for FileError {
fn from(err: io::Error) -> Self {
FileError::Io(err)
}
}
impl std::fmt::Display for FileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FileError::NotFound(path) => write!(f, "文件不存在: {}", path),
FileError::PermissionDenied(path) => write!(f, "权限不足: {}", path),
FileError::Io(e) => write!(f, "IO 错误: {}", e),
}
}
}
fn read_file_safe(path: &str) -> Result<String, FileError> {
let path = Path::new(path);
// 检查文件是否存在
if !path.exists() {
return Err(FileError::NotFound(path.display().to_string()));
}
// 读取文件内容
let contents = fs::read_to_string(path)?;
Ok(contents)
}
fn count_lines(content: &str) -> usize {
content.lines().count()
}
fn main() {
let file_path = "test.txt";
match read_file_safe(file_path) {
Ok(contents) => {
let line_count = count_lines(&contents);
println!("文件 '{}' 共有 {} 行", file_path, line_count);
println!("字符数: {}", contents.len());
}
Err(e) => {
eprintln!("错误: {}", e);
std::process::exit(1);
}
}
9.7 总结
Rust 的错误处理机制设计要点:
- panic! 用于不可恢复错误:程序遇到无法继续运行的情况时使用。
- Result 用于可恢复错误:调用者可以选择如何处理错误。
- ? 操作符简化错误传播:使代码简洁同时保持错误处理能力。
- 自定义错误类型提供清晰信息:让错误处理更加友好和健壮。
- 根据场景选择合适策略:原型用 unwrap,生产用 Result。
10. 泛型、Trait 与生命周期
本章介绍 Rust 的三个重要概念:泛型用于代码复用,Trait 定义共享行为,生命周期确保引用有效性。
10.1 泛型函数
泛型允许编写可适用于多种类型的函数,提高代码复用性。
// 泛型函数:找到两个值中的最大值
fn largest<T: std::cmp::PartialOrd>(x: T, y: T) -> T {
if x > y { x } else { y }
}
fn main() {
println!("最大整数: {}", largest(10, 20));
println!("最大浮点数: {}", largest(3.14, 2.71));
println!("最大字符: {}", largest('a', 'z'));
}
10.2 泛型结构体
结构体也可以使用泛型来存储多种类型的值。
// 定义一个泛型 Point 结构体,支持任意类型坐标
struct Point<T> {
x: T,
y: T,
}
// 泛型结构体可以有多个类型参数
struct Pair<T, U> {
first: T,
second: U,
}
fn main() {
// 同类型坐标点
let int_point = Point { x: 5, y: 10 };
let float_point = Point { x: 1.0, y: 4.0 };
println!("整数点: ({}, {})", int_point.x, int_point.y);
// 混合类型对
let pair = Pair { first: "hello", second: 42 };
println!("混合对: {} - {}", pair.first, pair.second);
}
10.3 泛型枚举
枚举的每个变体可以持有不同类型,实现类似 Option 和 Result 的灵活设计。
// Option<T> 是标准库定义的泛型枚举
// enum Option<T> { Some(T), None }
fn main() {
let some_number: Option<i32> = Some(5);
let some_string: Option<&str> = Some("a string");
let absent_number: Option<i32> = None;
// 使用 match 处理 Option
match some_number {
Some(n) => println!("有值: {}", n),
None => println!("无值"),
}
// Result<T, E> 是另一个泛型枚举
// enum Result<T, E> { Ok(T), Err(E) }
let ok_result: Result<i32, &str> = Ok(42);
let err_result: Result<i32, &str> = Err("错误信息");
println!("Ok 结果: {:?}", ok_result);
println!("Err 结果: {:?}", err_result);
}
10.4 Trait 定义
Trait 定义了一组行为(方法),可以被不同类型实现。
// 定义 Summary trait,规定实现者必须提供的方法
pub trait Summary {
fn summarize(&self) -> String;
}
// 为结构体实现 Trait
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.content)
}
}
fn main() {
let article = NewsArticle {
headline: String::from("Rust 发布新版本"),
location: String::from("中国"),
author: String::from("张三"),
content: String::from("Rust 1.70.0 正式发布..."),
};
let tweet = Tweet {
username: String::from("rustlang"),
content: String::from("Rust 是一门注重安全、并发和实用性的编程语言"),
reply: false,
retweet: false,
};
println!("文章摘要: {}", article.summarize());
println!("推文摘要: {}", tweet.summarize());
}
10.5 Trait 默认实现
Trait 可以提供默认方法实现,实现者可以选择覆盖或使用默认实现。
pub trait Summary {
// 默认实现
fn summarize_author(&self) -> String {
String::from("(未知作者)")
}
// 没有默认实现,必须由实现者提供
fn summarize(&self) -> String;
}
pub struct BlogPost {
pub author: String,
pub title: String,
pub content: String,
}
// 使用默认的 summarize_author 实现
impl Summary for BlogPost {
fn summarize(&self) -> String {
format!("《{}》- {}", self.title, self.summarize_author())
}
}
// 完全覆盖默认实现
pub struct Article {
pub author: String,
pub content: String,
}
impl Summary for Article {
fn summarize_author(&self) -> String {
format!("文章作者: {}", self.author)
}
fn summarize(&self) -> String {
format!("{} - {}", self.content, self.summarize_author())
}
}
fn main() {
let post = BlogPost {
author: String::from("李四"),
title: String::from("Rust 入门"),
content: String::from("Rust 是一门系统编程语言..."),
};
let article = Article {
author: String::from("王五"),
content: String::from("这是一篇技术文章..."),
};
println!("{}", post.summarize()); // 使用默认的 summarize_author
println!("{}", article.summarize()); // 使用覆盖的 summarize_author
}
10.6 Trait 作为参数
使用 impl Trait 语法可以让函数接受任何实现了特定 Trait 的类型。
pub trait Describable {
fn describe(&self) -> String;
}
struct Car { name: String, price: f64 }
struct Book { title: String, author: String }
impl Describable for Car {
fn describe(&self) -> String {
format!("汽车: {}, 价格: {}万", self.name, self.price)
}
}
impl Describable for Book {
fn describe(&self) -> String {
format!("书籍: {}, 作者: {}", self.title, self.author)
}
}
// impl Trait 语法糖:接受任何实现 Describable 的类型
fn print_description(item: &impl Describable) {
println!("{}", item.describe());
}
// 等价于 trait bound 语法
fn print_description_bound<T: Describable>(item: &T) {
println!("{}", item.describe());
}
fn main() {
let car = Car { name: String::from("特斯拉"), price: 30.0 };
let book = Book { title: String::from("Rust 编程"), author: String::from("张三") };
print_description(&car);
print_description(&book);
print_description_bound(&car);
}
10.7 Trait Bound
Trait Bound 是更正式的泛型约束语法,支持更复杂的需求。
use std::fmt::{Display, Debug};
// 多个 trait bound:类型必须同时实现 Display 和 Debug
fn notify<T: Display + Debug>(item: T) {
println!("{:?}", item);
println!("{}", item);
}
// where 子句:使复杂约束更清晰
fn some_function<T, U>(t: &T, u: &U) -> String
where
T: Display + Clone,
U: Clone + Debug,
{
format!("t: {:?}, u: {:?}", t, u)
}
// 使用 trait bound 实现条件返回
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
println!("最大数: {}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("最大字符: {}", largest(&chars));
}
10.8 生命周期标注
生命周期确保引用的有效性与作用域关联,防止悬垂引用。
// 生命周期标注语法:'a 表示生命周期参数
// 这个函数接受两个字符串引用,返回较长的那个
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let string1 = String::from("long string is long");
let string2 = String::from("short");
let result = longest(string1.as_str(), string2.as_str());
println!("较长的字符串: '{}'", result);
}
10.9 生命周期与结构体
结构体中的引用字段必须标注生命周期,表明引用何时有效。
// 结构体持有引用,必须标注生命周期
struct ImportantExcerpt<'a> {
part: &'a str, // 这个引用必须比结构体活得更久
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("找不到句号");
// 创建 excerpt,其生命周期与 novel 的引用绑定
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("引用内容: {}", excerpt.part);
}
10.10 生命周期省略规则
Rust 编译器会自动推断某些情况的生命周期,称为生命周期省略。
以下情况编译器会自动添加生命周期:
- 每个输入引用的生命周期独立推断
- 如果只有一个输入引用且有输出引用,则输出引用与输入引用生命周期相同
- 如果
self是输入引用,则输出引用生命周期与self相同
// 编译器自动推断生命周期,等价于 fn first_word<'a>(s: &'a str) -> &'a str
fn first_word(s: &str) -> &str {
match s.find(' ') {
Some(i) => &s[..i],
None => s,
}
}
// 不需要手动标注,编译器能推断
fn get_len(s: &str) -> usize {
s.len()
}
fn main() {
let s = String::from("hello world");
println!("第一个词: {}", first_word(&s));
println!("长度: {}", get_len(&s));
}
10.11 静态生命周期
'static 生命周期表示程序整个运行期间都有效的引用。
fn main() {
// 字符串字面量的生命周期是 'static
// 它们直接存储在程序二进制文件中
let s: &'static str = "我有静态生命周期,我永远存在";
println!("{}", s);
}
// &'static str 常见于错误消息中的字符串字面量
fn generic_function<T>(value: T)
where
T: std::fmt::Debug,
{
println!("{:?}", value);
}
10.12 综合示例
结合泛型、Trait 和生命周期的完整示例。
use std::fmt::Display;
// 泛型结构体结合生命周期
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("x >= y: x = {}", self.x);
} else {
println!("x < y: y = {}", self.y);
}
}
}
// 泛型函数结合 trait bound 和生命周期
fn longest_with_announcement<'a, T>(
x: &'a str,
y: &'a str,
ann: T,
) -> &'a str
where
T: Display,
{
println!("公告: {}", ann);
if x.len() > y.len() { x } else { y }
}
fn main() {
// 泛型结构体使用
let pair = Pair::new(5, 10);
pair.cmp_display();
let pair_str = Pair::new("apple", "banana");
pair_str.cmp_display();
// 复杂泛型函数
let s1 = String::from("long string");
let s2 = String::from("short");
let result = longest_with_announcement(&s1, &s2, "比较长度");
println!("较长的: {}", result);
}
10.13 总结
- 泛型:用于消除重复代码,支持多种类型的函数和结构体
- Trait:定义共享行为,通过
impl为类型实现特定行为 - Trait Bound:约束泛型类型必须实现某些 Trait
- 生命周期:确保引用始终有效,避免悬垂引用
- 生命周期省略:编译器自动推断简单情况下的生命周期
- 'static:表示引用在整个程序运行期间有效
11 测试
测试是 Rust 中确保代码正确性的重要手段。Rust 提供了完整的测试框架,包括单元测试、集成测试和文档测试。
11.1 #[test] 属性 - 标记测试函数
使用 #[test] 属性标记的函数会被当作测试函数来运行。测试函数不能有参数,不返回值。
// 在 src/lib.rs 或 src/main.rs 中
#[test]
fn it_works() {
// 测试函数,验证代码是否按预期工作
assert_eq!(2 + 2, 4);
}
#[test]
fn another() {
// 另一个测试函数
panic!("这个测试会失败");
}
运行测试:
cargo test
11.2 assert! 宏 - 基本断言
assert! 宏用于断言布尔表达式为 true。如果表达式为 false,测试会失败并显示指定的消息。
fn main() {
let is_even = 4 % 2 == 0;
assert!(is_even, "4 应该是偶数"); // 断言通过
}
// 实际的测试用例
#[test]
fn test_addition() {
let result = 2 + 2;
assert!(result == 4, "2 + 2 应该等于 4");
}
#[test]
fn test_string_not_empty() {
let s = String::from("hello");
assert!(!s.is_empty(), "字符串不应该为空");
}
11.3 assert_eq! 和 assert_ne! - 相等性测试
assert_eq! 断言两个值相等,assert_ne! 断言两个值不相等。如果断言失败,会显示具体的值。
#[test]
fn test_equal() {
assert_eq!(2 + 2, 4, "加法应该正确");
}
#[test]
fn test_not_equal() {
assert_ne!(2 + 2, 5, "2 + 2 不应该等于 5");
}
#[test]
fn test_string_equal() {
let s1 = String::from("hello");
let s2 = String::from("hello");
assert_eq!(s1, s2, "两个字符串应该相等");
}
11.4 #[should_panic] - 预期 panic 的测试
#[should_panic] 属性表示这个测试预期会 panic。如果测试正常完成而没有 panic,则测试失败。
// 测试代码在某种情况下应该 panic
struct Guess {
value: i32,
}
impl Guess {
fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess 值必须在 1 到 100 之间,当前值: {}", value);
}
Guess { value }
}
}
#[test]
#[should_panic(expected = "Guess 值必须在 1 到 100 之间")]
fn test_guess_too_large() {
Guess::new(200); // 应该 panic
}
#[test]
#[should_panic(expected = "Guess 值必须在 1 到 100 之间")]
fn test_guess_too_small() {
Guess::new(0); // 应该 panic
}
11.5 #[ignore] - 忽略某些测试
#[ignore] 属性告诉 Rust 跳过这个测试。使用 cargo test -- --ignored 可以运行被忽略的测试。
#[test]
#[ignore]
fn expensive_test() {
// 这个测试耗时较长,默认跳过
println!("这是一个耗时的测试");
assert_eq!(1, 1);
}
#[test]
fn normal_test() {
// 普通测试,每次都会运行
assert_eq!(2 + 2, 4);
}
运行忽略的测试:
cargo test -- --ignored
11.6 测试模块 #[cfg(test)]
#[cfg(test)] 模块用于存放测试代码,只有在运行 cargo test 时才会编译。这可以避免测试代码被包含在发布版本中。
// src/lib.rs 或 src/main.rs
// 待测试的代码
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// 测试模块
#[cfg(test)]
mod tests {
// 导入待测试的函数
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
}
11.7 单元测试 vs 集成测试
单元测试
单元测试针对单个模块或函数进行测试,验证其正确性。单元测试通常放在被测试代码的同一个文件中,使用 #[cfg(test)] 模块。
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_adds_two() {
assert_eq!(add(2, 2), 4);
}
}
集成测试
集成测试位于 tests 目录下(与 src 同级),用于测试多个模块组合在一起的行为。集成测试像外部代码一样使用你的库。
project/
├── Cargo.toml
├── src/
│ └── lib.rs
└── tests/
└── integration_test.rs
// tests/integration_test.rs
// 集成测试文件
use my_project; // 导入待测试的库
#[test]
fn test_add() {
assert_eq!(my_project::add(2, 2), 4);
}
#[test]
fn test_add_with_negative() {
assert_eq!(my_project::add(-1, 1), 0);
}
运行集成测试:
cargo test --test integration_test
11.8 运行测试 - cargo test
cargo test 会编译并运行所有测试。
# 运行所有测试
cargo test
# 运行指定测试
cargo test test_add
# 运行测试并显示 println! 输出
cargo test -- --nocapture
# 运行 tests 目录下特定文件的集成测试
cargo test --test integration_test
# 只运行单元测试
cargo test --lib
# 只运行文档测试
cargo test --doc
11.9 并行 vs 顺序运行测试
默认情况下,cargo test 会并行运行所有测试。这可以加快测试速度,但如果测试之间有依赖关系,可能需要顺序运行。
# 顺序运行测试(单线程)
cargo test -- --test-threads=1
# 查看测试输出而不等待所有测试完成
cargo test -- --nocapture
11.10 显示 println! 输出
默认情况下,测试通过时不会显示 println! 的输出。使用 --nocapture 可以显示输出。
#[test]
fn test_with_print() {
let value = 42;
println!("测试运行中,value = {}", value);
assert_eq!(value, 42);
}
cargo test test_with_print -- --nocapture
11.11 测试的组织结构
一个典型的测试组织结构:
// src/lib.rs
/// 加法函数
/// # Examples
/// ```
/// assert_eq!(add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// 减法函数
pub fn subtract(a: i32, b: i32) -> i32 {
a - b
}
// 单元测试模块
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
#[test]
fn test_subtract() {
assert_eq!(subtract(5, 3), 2);
}
#[test]
#[should_panic]
fn test_add_overflow() {
// 假设有溢出检测的加法
// add(i32::MAX, 1);
}
#[test]
#[ignore]
fn test_slow_operation() {
// 耗时操作
}
}
// tests/integration_test.rs
use my_project;
#[test]
fn test_add_and_subtract() {
let result = my_project::add(10, 5);
let final_result = my_project::subtract(result, 3);
assert_eq!(final_result, 12);
}
11.12 测试的最佳实践
- 测试函数命名:使用描述性的名称说明测试内容
```rust #[test] fn it_handles_empty_input() { }
#[test] fn it_returns_none_for_invalid_value() { } ```
- AAA 模式(Arrange-Act-Assert):
```rust #[test] fn test_addition() { // Arrange - 准备数据 let a = 2; let b = 3;
// Act - 执行操作
let result = add(a, b);
// Assert - 验证结果
assert_eq!(result, 5);
} ```
-
为公开 API 编写文档测试,确保文档示例可运行
-
使用
#[should_panic]测试边界条件 -
平衡单元测试和集成测试:单元测试验证细节,集成测试验证组件交互
11.13 总结
- #[test]:标记测试函数
- assert!:基本布尔断言
- assert_eq!:相等性断言
- assert_ne!:不等性断言
- #[should_panic]:预期 panic 的测试
- #[ignore]:忽略某些测试
- #[cfg(test)]:条件编译测试模块
- 单元测试:在源代码文件中测试单个模块
- 集成测试:在 tests 目录下测试组件交互
- cargo test:运行测试的命令
- --test-threads:控制并行或顺序运行
- --nocapture:显示 println! 输出
- --ignored:运行被忽略的测试
Chapter 13 闭包和迭代器
13.1 闭包基础
闭包是可以捕获环境中变量的匿名函数。
let add_one = |x| x + 1; // 单参数闭包
let add = |a, b| a + b; // 多参数闭包
let print_and_return = |x| { // 多行闭包
println!("x = {}", x);
x * 2
};
闭包语法:
|params| expression—— 单行闭包|params| { statements }—— 多行闭包
13.2 类型推断
Rust 会推断闭包的参数类型和返回类型。一旦推断,类型就固定。
let f = |x| x; // 推断:Fn(i32) -> i32
f(5); // 推断后,不能传其他类型
如果希望显式声明类型:
let f: fn(i32) -> i32 = |x| x + 1;
13.3 捕获环境
闭包可以捕获三种环境值:
fn main() {
let x = 10;
// 1. 不可变引用捕获
let equal_to_x = |y| y == x;
println!("{}", equal_to_x(10)); // true
// 2. 可变引用捕获
let mut y = 5;
let mut inc = || {
y += 1;
y
};
println!("{}", inc()); // 6
println!("{}", inc()); // 7
// 3. 获取所有权(move)
let data = vec![1, 2, 3];
let owned = move || {
println!("data: {:?}", data);
data.len()
};
println!("len: {}", owned()); // 3
}
13.4 Fn / FnMut / FnOnce trait
| Trait | 含义 | 调用方式 |
|---|---|---|
Fn |
可重复调用,不修改环境 | fn(&T) |
FnMut |
可重复调用,修改环境 | fn(&mut T) |
FnOnce |
只可调用一次,消耗环境 | fn(T) |
// FnOnce 示例:消耗捕获的值
fn consume<F>(f: F)
where
F: FnOnce(),
{
f();
}
let s = String::from("hello");
consume(|| println!("{}", s)); // s 在闭包中被消耗
// println!("{}", s); // 编译错误:s 已被消耗
// FnMut 示例:修改变量
fn modify<F>(mut f: F)
where
F: FnMut(),
{
f();
}
let mut count = 0;
modify(|| count += 1);
modify(|| count += 1);
println!("count = {}", count); // 2
13.5 迭代器创建
迭代器创建方式:
fn main() {
let v = vec![1, 2, 3];
// iter() - 不可变引用迭代
for val in v.iter() {
println!("{}", val);
}
// v 仍可继续使用
// iter_mut() - 可变引用迭代
let mut v2 = vec![1, 2, 3];
for val in v2.iter_mut() {
*val *= 2;
}
println!("{:?}", v2); // [2, 4, 6]
// into_iter() - 获取所有权迭代(消耗)
let v3 = vec![1, 2, 3];
for val in v3.into_iter() {
println!("{}", val);
}
// v3 已失效,不能再使用
}
13.6 Iterator trait
Iterator trait 定义:
pub trait Iterator {
type Item; // 迭代器产生的元素类型
fn next(&mut self) -> Option<Self::Item>;
// 其他默认方法...
}
实现自己的迭代器:
struct Counter {
count: u32,
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
fn main() {
let mut counter = Counter { count: 0 };
println!("{:?}", counter.next()); // Some(1)
println!("{:?}", counter.next()); // Some(2)
println!("{:?}", counter.next()); // Some(3)
println!("{:?}", counter.next()); // Some(4)
println!("{:?}", counter.next()); // Some(5)
println!("{:?}", counter.next()); // None
}
13.7 迭代器适配器
迭代器适配器将迭代器转换为其他迭代器:
fn main() {
let v = vec![1, 2, 3, 4, 5];
// map - 转换每个元素
let doubled: Vec<_> = v.iter().map(|x| x * 2).collect();
println!("{:?}", doubled); // [2, 4, 6, 8, 10]
// filter - 过滤元素
let evens: Vec<_> = v.iter().filter(|x| *x % 2 == 0).collect();
println!("{:?}", evens); // [2, 4]
// filter_map - 过滤 + 转换
let parsed: Vec<_> = ["1", "two", "3", "four"]
.iter()
.filter_map(|s| s.parse::<i32>().ok())
.collect();
println!("{:?}", parsed); // [1, 3]
// take - 取前 n 个
let first_three: Vec<_> = v.iter().take(3).cloned().collect();
println!("{:?}", first_three); // [1, 2, 3]
// skip - 跳过前 n 个
let skip_two: Vec<_> = v.iter().skip(2).cloned().collect();
println!("{:?}", skip_two); // [3, 4, 5]
// take_while - 取满足条件的元素直到条件不满足
let numbers = 1..=10;
let result: Vec<_> = numbers.take_while(|x| *x <= 5).collect();
println!("{:?}", result); // [1, 2, 3, 4, 5]
// enumerate - 带索引
let names = ["alice", "bob", "charlie"];
for (i, name) in names.iter().enumerate() {
println!("{}: {}", i, name);
}
// zip - 合并两个迭代器
let a = vec![1, 2, 3];
let b = vec!["one", "two", "three"];
let combined: Vec<_> = a.iter().zip(b.iter()).collect();
println!("{:?}", combined); // [(1, "one"), (2, "two"), (3, "three")]
// chain - 连接两个迭代器
let c = vec![1, 2];
let d = vec![3, 4];
let chained: Vec<_> = c.iter().chain(d.iter()).collect();
println!("{:?}", chained); // [1, 2, 3, 4]
// peekable - 可以预览下一个元素
let mut iter = v.iter().peekable();
println!("{:?}", iter.peek()); // Some(&1)
println!("{:?}", iter.peek()); // Some(&1)
println!("{:?}", iter.next()); // Some(1)
}
13.8 消费适配器
消费适配器会消耗迭代器:
fn main() {
let v = vec![1, 2, 3, 4, 5];
// sum - 求和
let total: i32 = v.iter().sum();
println!("sum = {}", total); // 15
// fold - 折叠
let product = v.iter().fold(1, |acc, x| acc * x);
println!("product = {}", product); // 120
// reduce - 聚合(类似 fold,但使用第一个元素作为初始值)
let max = v.iter().copied().reduce(|a, b| if a > b { a } else { b });
println!("max = {:?}", max); // Some(5)
// collect - 收集到容器
let collected: Vec<_> = (1..=5).collect();
println!("{:?}", collected); // [1, 2, 3, 4, 5]
// count - 计数
let cnt = v.iter().filter(|x| *x > 2).count();
println!("count = {}", cnt); // 3
// any / all / find
let has_even = v.iter().any(|x| x % 2 == 0);
let all_positive = v.iter().all(|x| *x > 0);
let first_gt_3 = v.iter().find(|x| **x > 3);
println!("any even: {}, all positive: {}, first > 3: {:?}", has_even, all_positive, first_gt_3);
// any even: true, all positive: true, first > 3: Some(4)
// min / max
let min = v.iter().min();
let max = v.iter().max();
println!("min = {:?}, max = {:?}", min, max); // min = Some(1), max = Some(5)
}
13.9 闭包作为参数
可以将闭包作为函数参数:
// 使用泛型 + trait bound
fn apply<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
f(x)
}
let result = apply(|x| x + 1, 5);
println!("result = {}", result); // 6
// 闭包作为返回值
fn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
move |x| x * factor
}
let times_two = make_multiplier(2);
let times_three = make_multiplier(3);
println!("{} * 2 = {}", 10, times_two(10)); // 20
println!("{} * 3 = {}", 10, times_three(10)); // 30
13.10 迭代器性能
迭代器是零成本抽象 —— 编译后代码与手写循环性能相当。
// 迭代器方式(推荐)
fn sum_squares(v: &[i32]) -> i32 {
v.iter()
.map(|x| x * x)
.filter(|x| x % 2 == 0)
.sum()
}
// 等价的手写循环
fn sum_squares_loop(v: &[i32]) -> i32 {
let mut total = 0;
for &x in v {
let squared = x * x;
if squared % 2 == 0 {
total += squared;
}
}
total
}
两种写法编译后生成的机器码几乎一致,迭代器没有额外运行时开销。
13.11 典型模式组合
fn main() {
// 链式调用
let result = (1..=100)
.filter(|x| x % 3 == 0) // 可被 3 整除
.map(|x| x * x) // 平方
.take(5) // 取前 5 个
.fold(0, |acc, x| acc + x); // 求和
println!("result = {}", result); // 2835
// partition - 按条件分组
let numbers = vec![1, 2, 3, 4, 5, 6];
let (evens, odds): (Vec<_>, Vec<_>) = numbers
.into_iter()
.partition(|x| *x % 2 == 0);
println!("evens: {:?}, odds: {:?}", evens, odds);
// evens: [2, 4, 6], odds: [1, 3, 5]
// flat_map - 展平嵌套结构
let words = vec!["hello", "world"];
let chars: Vec<_> = words.iter().flat_map(|s| s.chars()).collect();
println!("{:?}", chars);
// ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
// inspect - 调试用
let result: Vec<_> = (1..=5)
.inspect(|x| println!("before map: {}", x))
.map(|x| x * 2)
.inspect(|x| println!("after map: {}", x))
.collect();
// before map: 1
// after map: 2
// before map: 2
// after map: 4
// ...
}
13.12 总结
| 概念 | 说明 |
|---|---|
\|x\| expr |
闭包基本语法 |
| 类型推断 | Rust 自动推断闭包参数和返回类型 |
| 环境捕获 | 三种方式:引用、可变引用、获取所有权 |
Fn |
可重复调用,不修改环境 |
FnMut |
可重复调用,修改环境 |
FnOnce |
只可调用一次,消耗环境 |
iter() |
不可变引用迭代 |
iter_mut() |
可变引用迭代 |
into_iter() |
获取所有权迭代 |
map / filter |
迭代器适配器 |
sum / fold |
消费适配器 |
| 零成本抽象 | 迭代器编译后无额外开销 |
15 智能指针 (Smart Pointers)
智能指针是一类数据结构,它们表现类似指针,但也拥有额外的元数据和功能。Rust 中的智能指针通常基于 struct 实现,并实现了 Deref 和 Drop trait。
15.1 Box - 堆上分配数据
Box<T> 是最简单的智能指针,允许将数据存储在堆上而不是栈上。
fn main() {
// 使用 Box 在堆上存储一个 i32 值
let b = Box::new(5);
println!("b = {}", b); // b = 5
}
15.1.1 递归类型
在 Rust 中,编译时需要知道每种类型的大小。递归类型的值可以包含同类型的其他值,因此无法在编译时确定大小。Box<T> 解决了这个问题,因为它只知道指向堆上数据的指针大小。
// 枚举表示链表 - 可以递归
enum List {
Cons(i32, Box<List>), // Box 提供了固定大小的指针
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
// 使用 match 解构
match list {
Cons(head, tail) => println!("head = {}, tail = {:?}", head, tail),
Nil => println!("Empty list"),
}
}
15.1.2 Box 的常见用途
fn main() {
// 1. 在堆上存储大型数据
let large_array: Box<[i32; 10000]> = Box::new([0; 10000]);
// 2. 避免移动大数据的所有权开销
fn process_data(data: Box<[i32]>) {
println!("Received array with {} elements", data.len());
}
process_data(large_array);
// 3. 定义递归类型(见上方示例)
// 4. 实现 trait 时返回不确定大小的类型
trait Shape {
fn area(&self) -> f64;
}
struct Circle {
radius: f64,
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
// 返回 trait 对象时使用 Box
fn create_shape() -> Box<dyn Shape> {
Box::new(Circle { radius: 1.0 })
}
}
15.2 Deref Trait - 自定义解引用行为
通过实现 Deref trait,可以自定义解引用运算符 * 的行为。
use std::ops::Deref;
fn main() {
// 实现 Deref 后可以像使用引用一样使用智能指针
let x = 5;
let y = &x;
println!("x = {}, y = {}", x, *y); // 解引用获取值
// 使用 Box 同样可以解引用
let box_x = Box::new(x);
println!("box_x = {}", *box_x); // 自动解引用
}
15.2.1 自定义智能指针实现 Deref
use std::ops::Deref;
struct MyBox<T> {
value: T, // 内部存储的值
}
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox { value: x }
}
}
// 实现 Deref trait
impl<T> Deref for MyBox<T> {
// Target 是解引用后返回的类型
type Target = T;
fn deref(&self) -> &T {
// 返回值的引用
&self.value
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
// 实际上 y.deref() 返回 &5,然后 * 解引用得到 5
println!("x = {}, *y = {}", x, *y); // x = 5, *y = 5
}
15.2.2 Deref Coercion - 解引用强制转换
Deref Coercion 是 Rust 自动将某种类型转换为另一种类型的功能,只要后者实现了 Deref trait。
use std::ops::Deref;
struct MyBox<T> {
value: T,
}
impl<T> MyBox<T> {
fn new(x: T) -> Self {
MyBox { value: x }
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
fn hello(name: &str) {
println!("Hello, {}!", name);
}
fn main() {
let m = MyBox::new(String::from("Rust"));
// Deref Coercion 自动将 &MyBox<String> 转换为 &String,再转换为 &str
hello(&m);
// 等价于以下调用:
hello(&(*m)[..]); // 先解引用,再获取字符串切片
}
15.2.3 Deref Coercion 的转换规则
Rust 会自动进行以下转换:
&T→&U当T: Deref<Target=U>&mut T→&mut U当T: DerefMut<Target=U>&mut T→&U当T: Deref<Target=U>(可变引用可以强制转换为不可变引用)
use std::ops::Deref;
struct MyBox<T> {
value: T,
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
fn main() {
let mut m = MyBox::new(vec![1, 2, 3]);
// 可变引用可以解引用后获得可变引用
let v: &mut Vec<i32> = &mut *m;
v.push(4);
println!("{:?}", m.value); // [1, 2, 3, 4]
}
15.3 Drop Trait - 清理逻辑
Drop trait 允许在值离开作用域时执行自定义代码。Rust 自动为大多数智能指针实现此 trait。
struct CustomSmartPointer {
data: String,
}
// 实现 Drop trait
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
// 在值被 drop 之前调用
println!("Dropping CustomSmartPointer with data: {}", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("my data"),
};
println!("Created CustomSmartPointer");
// c 在此离开作用域时会自动调用 drop
}
// 输出:
// Created CustomSmartPointer
// Dropping CustomSmartPointer with data: my data
15.3.1 提前 drop 值
可以使用 std::mem::drop 提前 drop 一个值:
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping: {}", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("first"),
};
println!("Before drop");
drop(c); // 手动调用 drop 提前清理
println!("After drop");
let d = CustomSmartPointer {
data: String::from("second"),
};
println!("End of main");
}
// 输出:
// Before drop
// Dropping: first
// After drop
// End of main
// Dropping: second
15.3.2 Box 和 Drop
Box<T> 自动在离开作用域时释放堆上的内存:
fn main() {
{
let boxed = Box::new(5);
println!("boxed = {}", boxed);
} // boxed 在这里自动释放堆内存
println!("Left the scope");
}
15.4 Rc - 引用计数,多重所有权
Rc<T> (Reference Counting) 允许同一数据有多个所有者,通过引用计数追踪所有者的数量。
use std::rc::Rc;
fn main() {
let data = Rc::new(vec![1, 2, 3]);
println!("初始引用计数: {}", Rc::strong_count(&data)); // 1
// 克隆 Rc 不会深拷贝数据,只是增加引用计数
let data2 = Rc::clone(&data);
println!("克隆后引用计数: {}", Rc::strong_count(&data)); // 2
// data 和 data2 共享所有权
println!("data: {:?}", data);
println!("data2: {:?}", data2);
{
let data3 = Rc::clone(&data);
println!("内部作用域引用计数: {}", Rc::strong_count(&data3)); // 3
} // data3 在这里被 drop,计数减 1
println!("离开内部作用域后计数: {}", Rc::strong_count(&data)); // 2
} // data 和 data2 都被 drop,计数为 0,数据被释放
15.4.1 Rc 只能用于单线程
Rc<T> 是 非线程安全 的,用于单线程场景。如果需要多线程共享所有权,使用 Arc<T> (Atomic Reference Counting)。
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
// 创建共享的列表
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("a 的引用计数: {}", Rc::strong_count(&a)); // 1
// b 共享 a 的所有权
let b = Cons(3, Rc::clone(&a));
println!("b 创建后引用计数: {}", Rc::strong_count(&a)); // 2
// c 也共享 a 的所有权
let c = Cons(4, Rc::clone(&a));
println!("c 创建后引用计数: {}", Rc::strong_count(&a)); // 3
} // a, b, c 都被 drop,引用计数归零,内存释放
15.5 RefCell - 运行时借用检查
RefCell<T> 允许 在运行时 进行借用检查,在编译时借用规则可能被违反但运行时是安全的情况下使用。
15.5.1 借用规则回顾
- 在任何时候,只能有 一个可变引用,或者 多个不可变引用。
- 引用必须总是有效的。
fn main() {
let mut s = String::from("hello");
// 不可变借用
let r1 = &s;
// 可变借用(在同一作用域内不能再有不可变借用)
let r2 = &mut s;
// 错误!同时存在不可变引用和可变引用
println!("{}, {}", r1, r2);
}
15.5.2 RefCell 的内部可变性
RefCell<T> 提供了 内部可变性 (Interior Mutability) 模式,允许在不可变引用的地方修改数据。
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
// 通过不可变引用借用
let r1 = data.borrow(); // 返回 Ref<T>
let r2 = data.borrow(); // 可以在有不可变借用时再获取不可变借用
println!("r1 = {}, r2 = {}", r1, r2);
// Ref<T> 在离开作用域时自动释放借用
// 可变借用
let mut r3 = data.borrow_mut(); // 返回 RefMut<T>
*r3 = 10;
println!("r3 = {}", r3);
} // borrow_mut 释放后,可以再次 borrow
15.5.3 RefCell 借用检查时机
borrow(): 返回Ref<T>,不可变借用。运行时检查确保没有可变借用存在。borrow_mut(): 返回RefMut<T>,可变借用。运行时检查确保没有其他借用存在。
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
// 不可变借用
{
let r = data.borrow();
println!("不可变借用: {:?}", r);
} // r 离开作用域
// 可变借用
{
let mut r = data.borrow_mut();
r.push(4);
println!("可变借用后: {:?}", r);
}
// 借用检查在运行时进行
println!("最终数据: {:?}", data.borrow());
}
15.5.4 借用冲突示例
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
let r1 = data.borrow();
let r2 = data.borrow();
// 尝试获取可变借用 - 运行时 panic!
// let r3 = data.borrow_mut();
println!("r1: {:?}, r2: {:?}", r1, r2);
// r1 和 r2 离开作用域,释放借用
// 现在可以可变借用了
let mut r4 = data.borrow_mut();
r4.push(4);
println!("r4: {:?}", r4);
}
15.6 Cell 和 RefCell 对比
| 特性 | Cell<T> |
RefCell<T> |
|---|---|---|
| 适用类型 | Copy 类型 (如 i32, bool) |
非 Copy 类型 |
| 获取值 | .get() 返回拷贝的值 |
.borrow() 返回引用 |
| 修改值 | .set() 或 .with() |
.borrow_mut() |
| 借用检查 | 编译时 | 运行时 |
| 开销 | 低(内联,无额外检查) | 高(有运行时检查开销) |
| 线程安全 | 否 | 否 |
use std::cell::{Cell, RefCell};
fn main() {
// Cell<T> - 适用于 Copy 类型
let cell = Cell::new(5);
let v1 = cell.get(); // 获取值的拷贝
cell.set(10); // 设置新值
println!("Cell: get={}, value={}", v1, cell.get());
// RefCell<T> - 适用于非 Copy 类型
let refcell = RefCell::new(vec![1, 2, 3]);
{
let r = refcell.borrow(); // 获取不可变引用
println!("RefCell borrow: {:?}", r);
}
{
let mut r = refcell.borrow_mut();
r.push(4); // 获取可变引用并修改
println!("RefCell borrow_mut: {:?}", r);
}
println!("RefCell final: {:?}", refcell.borrow());
}
15.6.1 组合使用 Rc 和 RefCell
常见的模式是 Rc<RefCell<T>>,允许多个 Rc 共享可变数据:
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
// 创建一个共享的可变数据
let shared = Rc::new(RefCell::new(vec![1, 2, 3]));
// 克隆共享数据(不拷贝内容,只是增加引用计数)
let s1 = Rc::clone(&shared);
let s2 = Rc::clone(&shared);
// 通过 s1 修改数据
s1.borrow_mut().push(4);
// 通过 s2 也能看到修改
println!("s2: {:?}", s2.borrow());
// 所有 Rc 都能看到最新的数据
println!("shared: {:?}", shared.borrow());
println!("引用计数: {}", Rc::strong_count(&shared)); // 3
}
15.7 引用循环和内存泄漏
Rust 的内存回收系统会自动释放不再使用的内存,但 引用循环 可能导致内存泄漏(无法被回收)。
15.7.1 创建引用循环
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>), // 指向下一个元素的可变引用
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match self {
List::Cons(_, item) => Some(item),
List::Nil => None,
}
}
}
use crate::List::{Cons, Nil};
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("a 的初始引用计数: {}", Rc::strong_count(&a)); // 1
println!("a 的下一个元素: {:?}", a.tail());
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("b 创建后 a 的引用计数: {}", Rc::strong_count(&a)); // 2
println!("b 的下一个元素: {:?}", b.tail());
// 创建循环: a 的下一个元素指向 b
if let Some(link) = a.tail() {
link.borrow_mut().assign(Rc::clone(&b));
}
println!("创建循环后 b 的引用计数: {}", Rc::strong_count(&b)); // 2
println!("创建循环后 a 的引用计数: {}", Rc::strong_count(&a)); // 2
// 尝试遍历会导致无限递归
// println!("a next = {:?}", a.tail()); // 运行时 stack overflow
}
15.7.2 避免引用循环:使用 Weak
Weak<T> 是 Rc<T> 的一个版本,它不拥有数据,不会增加引用计数。
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Debug)]
struct Node {
value: i32,
// 使用 Weak 避免循环引用
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
// 创建一个叶子节点
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()), // 初始没有父节点
children: RefCell::new(vec![]),
});
println!("leaf 的引用计数: {}", Rc::strong_count(&leaf)); // 1
println!("leaf 的父节点: {:?}", leaf.parent.borrow().upgrade());
// 创建一个枝节点,拥有 leaf 作为子节点
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
// 设置 leaf 的父节点指向 branch
// 使用 Weak 不会增加 branch 的引用计数
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
println!("leaf 的父节点: {:?}", leaf.parent.borrow().upgrade());
println!("branch 的引用计数: {}", Rc::strong_count(&branch)); // 2
println!("leaf 的引用计数: {}", Rc::strong_count(&leaf)); // 2
}
15.7.3 Weak::upgrade 和 Weak::downgrade
Rc::downgrade(&rc)→Weak<T>: 从Rc创建Weak,不增加引用计数weak.upgrade()→Option<Rc<T>>: 将Weak尝试转换为Rc,如果值已被 drop 返回None
use std::cell::RefCell;
use std::rc::{Rc, Weak};
fn main() {
let strong = Rc::new(5);
let weak = Rc::downgrade(&strong);
// upgrade 返回 Option<Rc<T>>
if let Some(strong_ref) = weak.upgrade() {
println!("升级成功: {}", strong_ref);
}
drop(strong); // 释放 strong
// 升级失败,因为值已被 drop
if weak.upgrade().is_none() {
println!("升级失败,值已被释放");
}
}
15.7.4 引用循环内存泄漏示例总结
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
struct Person {
name: String,
friends: RefCell<Vec<Rc<Person>>>, // 朋友列表
}
fn main() {
// 创建两个人
let alice = Rc::new(Person {
name: String::from("Alice"),
friends: RefCell::new(vec![]),
});
let bob = Rc::new(Person {
name: String::from("Bob"),
friends: RefCell::new(vec![]),
});
// 让他们互相引用(引用循环)
alice.friends.borrow_mut().push(Rc::clone(&bob));
bob.friends.borrow_mut().push(Rc::clone(&alice));
println!("Alice 引用计数: {}", Rc::strong_count(&alice)); // 2
println!("Bob 引用计数: {}", Rc::strong_count(&bob)); // 2
// 即使离开作用域,引用计数也不会归零
// 因为 alice -> bob -> alice 形成循环
// 内存泄漏!
}
15.8 智能指针选择指南
| 场景 | 推荐类型 | 说明 |
|---|---|---|
| 单所有权,堆上分配 | Box<T> |
最简单的堆分配 |
| 多个所有权共享 | Rc<T> |
单线程,多所有者 |
| 多个线程共享 | Arc<T> |
多线程版本 |
| 需要可变访问 | RefCell<T> |
运行时借用检查 |
| 单线程多所有者可变 | Rc<RefCell<T>> |
组合使用 |
| 多线程可变共享 | Arc<Mutex<T>> |
原子锁保护 |
| 避免循环引用 | Weak<T> |
弱引用 |
15.9 章节总结
| 概念 | 说明 |
|---|---|
Box<T> |
堆上分配,固定大小指针,用于递归类型 |
Deref trait |
自定义 * 解引用行为 |
DerefCoercion |
自动类型转换,&MyBox→&T |
Drop trait |
值离开作用域时自动调用 |
Rc<T> |
引用计数,单线程多所有权 |
RefCell<T> |
运行时借用检查,内部可变性 |
Cell<T> |
编译时借用检查,Copy 类型 |
Weak<T> |
弱引用,避免循环引用 |
| 引用循环 | Rc<RefCell<Rc<...>>> 可能导致内存泄漏 |
第十六章:Fearless Concurrency(无畏并发)
Rust 的并发模型是其最强大的特性之一,它通过所有权系统和类型系统,在编译时就能消除数据竞争和大多数并发错误,让开发者能够写出安全且高效的并发代码。
16.1 线程创建 - thread::spawn
Rust 标准库提供了 std::thread 模块来创建和管理线程。使用 thread::spawn 可以创建一个新线程,执行异步任务。
use std::thread;
use std::time::Duration;
fn main() {
// 使用 thread::spawn 创建新线程
// spawn 接收一个闭包,闭包中包含要在新线程中执行的代码
let handle = thread::spawn(|| {
// 这段代码会在新线程中执行
for i in 1..=5 {
println!("子线程打印: {}", i);
// 使用 thread::sleep 模拟耗时操作
thread::sleep(Duration::from_millis(100));
}
});
// 主线程继续执行自己的任务
for i in 1..=5 {
println!("主线程打印: {}", i);
thread::sleep(Duration::from_millis(100));
}
// 等待子线程完成
// join() 会阻塞主线程,直到子线程执行完毕
handle.join().unwrap();
println!("主线程执行完毕");
}
thread::spawn 的返回类型是 JoinHandle<T>,它是一个句柄,用于等待线程完成并获取其返回值。如果线程panic,join() 会返回错误。
16.2 线程等待 - JoinHandle
JoinHandle 提供了 join() 方法来等待线程完成执行。
use std::thread;
use std::time::Duration;
fn main() {
// 创建线程并获取 handle
let handle = thread::spawn(|| {
println!("子线程开始执行");
thread::sleep(Duration::from_secs(1));
42 // 子线程的返回值
});
// 在 handle 上调用 join() 阻塞主线程
// 直到子线程完成
let result = handle.join().unwrap();
println!("子线程返回值为: {}", result);
println!("主线程继续执行");
}
move 闭包与线程
在 thread::spawn 中使用闭包时,如果闭包需要捕获外部变量,需要使用 move 关键字将变量的所有权转移到新线程中。
use std::thread;
fn main() {
let data = vec![1, 2, 3, 4, 5];
// 使用 move 将 data 的所有权转移到子线程
let handle = thread::spawn(move || {
println!("子线程中访问 data: {:?}", data);
// data 在这里被使用,之后会被 drop
data.iter().sum::<i32>()
});
// 这里不能再使用 data,因为所有权已经转移
// println!("{:?}", data); // 编译错误!
let sum = handle.join().unwrap();
println!("子线程计算结果: {}", sum);
}
等待多个线程
use std::thread;
use std::time::Duration;
fn main() {
let mut handles = vec![];
// 创建多个线程
for i in 0..5 {
let handle = thread::spawn(move || {
println!("线程 {} 开始", i);
thread::sleep(Duration::from_millis(100 * i as u64));
println!("线程 {} 结束", i);
i * i
});
handles.push(handle);
}
// 等待所有线程完成并收集结果
for (i, handle) in handles.into_iter().enumerate() {
let result = handle.join().unwrap();
println!("线程 {} 的结果: {}", i, result);
}
println!("所有线程执行完毕");
}
16.3 线程间传递消息 - mpsc channel
Rust 提供了消息传递(Message Passing)机制来实现线程间通信。标准库的 std::sync::mpsc 模块实现了多生产者单消费者(Multiple Producers, Single Consumer)模式。
创建消息通道
use std::sync::mpsc;
use std::thread;
fn main() {
// 创建消息通道
// mpsc::channel() 返回一个元组 (tx, rx)
// tx 是发送端,rx 是接收端
let (tx, rx) = mpsc::channel();
// 创建子线程发送消息
let handle = thread::spawn(move || {
let msg = String::from("来自子线程的消息");
tx.send(msg).unwrap();
// msg 已经被发送,不能再使用
});
// 主线程接收消息
// recv() 会阻塞,直到收到消息
let received = rx.recv().unwrap();
println!("主线程收到: {}", received);
handle.join().unwrap();
}
channel 的基本操作
tx.send(value): 发送消息,返回Result<(), SendError<T>>rx.recv(): 接收消息,返回Result<T, RecvError>,阻塞直到收到消息rx.try_recv(): 非阻塞接收,尝试立即获取消息,返回Result<T, TryRecvError>
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
// 子线程发送多条消息
let producer = thread::spawn(move || {
for i in 1..=5 {
tx.send(i).unwrap();
thread::sleep(Duration::from_millis(100));
}
// tx 在这里被 drop,表示发送结束
});
// 主线程接收消息直到通道关闭
// rx.iter() 会迭代接收消息,直到发送端被 drop
for received in rx {
println!("收到: {}", received);
}
producer.join().unwrap();
println!("消息接收完毕");
}
16.4 多生产者单消费者
当有多个线程需要向同一个通道发送消息时,只需要克隆发送端(tx)。每个克隆的 tx 都可以独立发送消息,而接收端(rx)只有一个。
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// 创建通道
let (tx, rx) = mpsc::channel();
// 创建多个发送者(生产者)
// 注意:需要重新启用 move,因为 tx 在循环中被移动
let mut tx_handles = vec![];
for i in 0..3 {
let tx_clone = tx.clone();
let handle = thread::spawn(move || {
for j in 1..=3 {
let msg = format!("生产者{} 消息{}", i, j);
tx_clone.send(msg).unwrap();
thread::sleep(Duration::from_millis(50));
}
});
tx_handles.push(handle);
}
// 显式 drop 原始 tx,确保只有一个接收者
drop(tx);
// 单个接收者(消费者)接收所有消息
let mut msg_count = 0;
for received in rx {
msg_count += 1;
println!("收到: {}", received);
}
// 等待所有生产者线程结束
for handle in tx_handles {
handle.join().unwrap();
}
println!("总共收到 {} 条消息", msg_count);
}
发送端所有权
tx.send() 会获取值的所有权并发送,接收端收到的是值的所有权。这意味着可以通过 channel 传递任何类型的数据。
use std::sync::mpsc;
use std::thread;
#[derive(Debug)]
struct Data {
content: String,
value: i32,
}
fn main() {
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
let data = Data {
content: String::from("hello"),
value: 42,
};
tx.send(data).unwrap();
// data 已被发送,不能再访问
});
let received = rx.recv().unwrap();
println!("收到数据: {:?}", received);
handle.join().unwrap();
}
16.5 Mutex - 互斥锁
Mutex(Mutual Exclusion,互斥锁)是一种用于保护共享数据的同步原语。同一时间只允许一个线程访问受保护的数据。
Mutex 的基本使用
use std::sync::Mutex;
fn main() {
// 创建 Mutex,初始值为 5
let counter = Mutex::new(5);
{
// lock() 返回一个 MutexGuard,它实现了 Deref
// 可以像引用一样使用内部的值
let mut num = counter.lock().unwrap();
*num += 1;
println!("counter 的值为: {}", *num);
} // lock() 返回的 Guard 在这里被 drop,锁被释放
println!("counter 最终值为: {:?}", counter);
}
在多线程中使用 Mutex
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Mutex::new(0);
let mut handles = vec![];
for _ in 0..10 {
let counter_ref = &counter;
let handle = thread::spawn(move || {
let mut num = counter_ref.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("最终计数: {}", *counter.lock().unwrap());
}
16.6 锁的创建和访问
创建 Mutex
Mutex::new(value) 创建一个新的互斥锁,并将初始值设置为 value。
use std::sync::Mutex;
let mutex = Mutex::new(0);
访问受保护的数据
使用 lock() 方法获取锁:
lock()返回Result<MutexGuard<T>, PoisonError<MutexGuard<T>>>- 如果锁被成功获取,返回一个
MutexGuard,它是一个智能指针,实现了Deref和DerefMut unwrap()用于在锁被成功获取时提取MutexGuard
use std::sync::Mutex;
let mutex = Mutex::new(vec![1, 2, 3]);
// 获取锁并修改数据
{
let mut guard = mutex.lock().unwrap();
guard.push(4);
guard.push(5);
} // guard 在这里被 drop,锁被释放
println!("{:?}", mutex);
MutexGuard 的特性
MutexGuard<T> 实现了 Deref<Target = T> 和 DerefMut,可以像引用一样使用。当它被 drop 时,锁会自动释放。
use std::sync::Mutex;
fn main() {
let mutex = Mutex::new(1);
// 方式1:使用 lock() 获取 guard
let mut g1 = mutex.lock().unwrap();
*g1 = 10;
drop(g1); // 显式释放锁
// 方式2:使用作用域自动释放
{
let mut g2 = mutex.lock().unwrap();
*g2 = 20;
} // 锁在这里自动释放
}
16.7 死锁避免
死锁发生在两个或多个线程互相等待对方释放锁时。Rust 的类型系统无法完全防止死锁,但遵循一些最佳实践可以避免大多数死锁情况。
避免死锁的原则
- 尽量缩短持锁时间:只在必要时持有锁
- 避免在持有锁时调用用户代码:用户代码可能再次获取锁
- 按固定顺序获取多个锁:如果需要多个锁,所有线程都按相同顺序获取
死锁示例(演示目的)
use std::sync::{Mutex, Arc};
use std::thread;
// 这是一个可能产生死锁的示例,请勿模仿
fn deadlock_example() {
let mutex1 = Arc::new(Mutex::new(1));
let mutex2 = Arc::new(Mutex::new(2));
let m1 = mutex1.clone();
let m2 = mutex2.clone();
// 线程1: 先获取 mutex1,再获取 mutex2
let t1 = thread::spawn(move || {
let _g1 = m1.lock().unwrap();
println!("线程1 持有 mutex1");
thread::sleep(std::time::Duration::from_millis(10));
let _g2 = m2.lock().unwrap(); // 尝试获取 mutex2
println!("线程1 持有 mutex2");
});
// 线程2: 先获取 mutex2,再获取 mutex1
let t2 = thread::spawn(move || {
let _g2 = m2.lock().unwrap();
println!("线程2 持有 mutex2");
thread::sleep(std::time::Duration::from_millis(10));
let _g1 = m1.lock().unwrap(); // 尝试获取 mutex1
println!("线程2 持有 mutex1");
});
t1.join().unwrap();
t2.join().unwrap();
}
安全的锁获取模式
use std::sync::{Mutex, Arc};
use std::thread;
fn main() {
let mutex = Arc::new(Mutex::new(0));
let mut handles = vec![];
for i in 0..5 {
let mutex_clone = mutex.clone();
let handle = thread::spawn(move || {
// 获取锁,修改值,立即释放
let mut num = mutex_clone.lock().unwrap();
*num += i;
// 锁在作用域结束时自动释放
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("最终结果: {}", *mutex.lock().unwrap());
}
16.8 Arc - 原子引用计数
Arc<T>(Atomic Reference Counting)是 Rc<T> 的线程安全版本。它提供了多个线程共享所有权的机制,通过原子操作来管理引用计数,确保计数操作的线程安全性。
Arc 与 Rc 的区别
Rc<T>: 非线程安全,用于单线程场景Arc<T>: 线程安全,使用原子操作,适用于多线程
use std::sync::Arc;
use std::thread;
fn main() {
// 创建 Arc,持有 Vec
let data = Arc::new(vec![1, 2, 3, 4, 5]);
let mut handles = vec![];
// 创建多个线程,每个线程克隆 Arc
for i in 0..3 {
let data_clone = data.clone();
let handle = thread::spawn(move || {
println!("线程{} 看到的数据: {:?}", i, data_clone);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("主线程看到的数据: {:?}", data);
}
Arc 与 Mutex 组合使用
由于 Arc 只提供共享所有权,不提供可变访问,通常需要与 Mutex 组合使用来实现多线程安全的可变共享。
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Arc 提供共享所有权,Mutex 提供可变形参访问
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter_clone = counter.clone();
let handle = thread::spawn(move || {
let mut num = counter_clone.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("最终计数: {}", *counter.lock().unwrap());
}
Arc 的只读共享
如果只需要在多线程间共享只读数据,可以直接使用 Arc<T>:
use std::sync::Arc;
use std::thread;
fn main() {
let data = Arc::new(vec![1, 2, 3, 4, 5]);
let handles: Vec<_> = (0..3)
.map(|i| {
let data = data.clone();
thread::spawn(move || {
println!("线程 {}: {:?}", i, data);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
16.9 Send 和 Sync trait
Rust 的并发模型建立在两个特殊的 trait 之上:Send 和 Sync。它们是 Rust 内存安全保证的重要组成部分。
Send - 允许线程间转移所有权
Send trait 表明类型的所有权可以在线程间转移。如果一个类型实现了 Send,那么可以将其值发送到另一个线程。
- 基本类型(如
i32,f64,bool等)都实现了Send Rc<T>没有实现Send(非线程安全)- 自定义类型如果所有成员都实现了
Send,则自动实现Send
use std::thread;
// i32 实现了 Send,所以可以在线程间传递
fn demonstrate_send() {
let value = 42;
let handle = thread::spawn(move || {
println!("收到值: {}", value);
});
handle.join().unwrap();
}
Sync - 允许线程间共享引用
Sync trait 表明一个类型可以安全地被多个线程同时持有引用(即 &T 是 Send 的)。如果 &T 可以安全地发送到另一个线程,那么 T 就是 Sync 的。
- 基本类型都实现了
Sync Rc<T>没有实现Sync(非线程安全)Mutex<T>实现了Sync(锁保护了内部数据)
use std::sync::Mutex;
use std::thread;
fn demonstrate_sync() {
let mutex = Mutex::new(vec![1, 2, 3]);
// &MutexVec 是 Send 的,因为 Mutex 实现了 Sync
let handle = thread::spawn(move || {
let mut data = mutex.lock().unwrap();
data.push(4);
});
handle.join().unwrap();
println!("{:?}", mutex);
}
Send 和 Sync 的自动实现
大多数类型都会自动实现 Send 和 Sync:
- 如果
T是Sync,则&T是Send - 如果
T的所有字段都实现了Send,则T是Send - 如果
T的所有字段都实现了Sync,则T是Sync
手动实现 Send 和 Sync
通常不需要手动实现这两个 trait,它们是标记 trait(marker trait),没有方法。只是用于编译器的静态检查。
// 这个类型会自动实现 Send 和 Sync
// 因为它的所有字段都实现了这些 trait
struct MyData {
value: i32,
name: String,
}
// 可以通过 impl 显式标注(但一般不需要)
unsafe impl Send for MyData {}
unsafe impl Sync for MyData {}
ThreadState 示例 - 综合使用
use std::sync::{Arc, Mutex};
use std::thread;
// 一个线程安全的计数器
struct Counter {
count: u64,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
fn increment(&mut self) {
self.count += 1;
}
fn get(&self) -> u64 {
self.count
}
}
fn main() {
// 使用 Arc 共享 Counter,使用 Mutex 提供可变形参访问
let counter = Arc::new(Mutex::new(Counter::new()));
let mut handles = vec![];
// 创建多个线程同时修改计数器
for i in 0..10 {
let counter_clone = counter.clone();
let handle = thread::spawn(move || {
let mut c = counter_clone.lock().unwrap();
c.increment();
println!("线程 {} 执行完毕", i);
});
handles.push(handle);
}
// 等待所有线程完成
for handle in handles {
handle.join().unwrap();
}
// 输出最终计数
let final_count = counter.lock().unwrap().get();
println!("最终计数: {}", final_count);
}
16.10 章节总结
| 概念 | 说明 |
|---|---|
thread::spawn |
创建新线程,执行闭包中的代码 |
JoinHandle |
线程句柄,通过 join() 等待线程完成 |
move 闭包 |
将外部变量所有权转移到新线程 |
mpsc::channel |
多生产者单消费者通道 |
tx.send() |
发送消息,转移所有权 |
rx.recv() |
接收消息,阻塞等待 |
Mutex<T> |
互斥锁,保护共享数据 |
MutexGuard |
锁的智能指针,drop 时自动释放锁 |
| 死锁 | 多个线程互相等待对方释放锁 |
Arc<T> |
原子引用计数,线程安全的多所有权 |
Arc<Mutex<T>> |
多线程可变共享的常用组合 |
Send trait |
类型可以在线程间转移所有权 |
Sync trait |
类型可以在线程间共享引用 |
Rust 的并发模型通过所有权、生命周期和 trait 系统,在编译期就能防止数据竞争(data race)和大多数并发错误,让开发者能够写出既安全又高效的并发代码。