Skip to content

基础学习

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,它有两个可能的值:truefalse。布尔值通常用于条件判断和控制流。以下是一些示例:

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 中都有一个所有者,当所有者离开作用域时,值会被自动释放。以下是一些示例:

所有权三原则

  1. Rust 中的每个值都有一个所有者。
  2. 每个值同时只能有一个所有者。
  3. 当所有者离开作用域时,值会被自动释放。

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 的值
}

总结

  1. fn xxx(par) → 所有权转移(如果类型未实现 Copy
  2. fn xxx(&par) → 不可变借用,所有权不转移
  3. 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
}

切片的注意事项

  1. 切片的索引必须在有效范围内,否则会导致 panic
  2. 字符串切片的索引必须落在 UTF-8 字符边界上
  3. 切片不拥有数据的所有权,当原数据被释放时,切片将变得无效
  4. 可变切片和不可变切片不能同时存在(借用规则)
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);
}

小结

  1. 枚举定义:枚举用于定义有限的类型变体,每个变体可以关联不同的数据
  2. Option:Rust 处理空值的标准方式,Some(T) 表示有值,None 表示无值
  3. match:强大的模式匹配工具,必须穷尽所有可能的情况
  4. 绑定值:模式匹配时可以绑定变体关联的值
  5. _ 通配符:匹配所有其他情况
  6. if let:简化只关心一个匹配的场景
  7. matches! 宏:返回布尔值的模式测试
  8. 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
}

结构体方法设计建议

  1. 方法应该访问 self:如果函数需要访问结构体的字段,将其定义为方法
  2. 使用 &self:如果方法只读取数据,使用不可变引用
  3. 使用 &mut self:如果方法需要修改数据,使用可变引用
  4. 使用关联函数创建实例:将构造逻辑放在关联函数中(如 new()
  5. 保持方法简洁:每个方法只做一件事

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 存储键值对,键必须是可以哈希的类型(实现了 EqHash 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 的情况

  1. 不可能发生的情况:使用 unwrap() 表明某个情况在逻辑上不可能发生。
fn main() {
    // 永远不会是 None,因为 Some(42) 明显存在
    let value = Some(42).unwrap();
    println!("值: {}", value);
}
  1. 原型开发:快速编写代码,验证逻辑。
fn main() {
    // 原型阶段使用 unwrap 快速测试
    let config = std::env::var("CONFIG_PATH").unwrap();
}
  1. 测试代码:panic 是测试失败的一种形式。
#[cfg(test)]
mod tests {
    #[test]
    fn test_add() {
        assert_eq!(2 + 2, 4);
    }
}
  1. 示例代码:展示概念,假设输入总是有效。
fn main() {
    // 示例中假设用户会输入数字
    let input = "42";
    let number: i32 = input.parse().unwrap();
    println!("数字: {}", number);
}

应该返回 Result 的情况

  1. 可能失败的操作:文件、网络、用户输入等。
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)
}
  1. 需要调用者决策:错误处理方式取决于调用者。
fn parse_integer(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.parse::<i32>()
}
  1. 库代码:将错误信息传递给调用者。
// 库函数应该返回 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 的错误处理机制设计要点:

  1. panic! 用于不可恢复错误:程序遇到无法继续运行的情况时使用。
  2. Result 用于可恢复错误:调用者可以选择如何处理错误。
  3. ? 操作符简化错误传播:使代码简洁同时保持错误处理能力。
  4. 自定义错误类型提供清晰信息:让错误处理更加友好和健壮。
  5. 根据场景选择合适策略:原型用 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 编译器会自动推断某些情况的生命周期,称为生命周期省略。

以下情况编译器会自动添加生命周期:

  1. 每个输入引用的生命周期独立推断
  2. 如果只有一个输入引用且有输出引用,则输出引用与输入引用生命周期相同
  3. 如果 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 总结

  1. 泛型:用于消除重复代码,支持多种类型的函数和结构体
  2. Trait:定义共享行为,通过 impl 为类型实现特定行为
  3. Trait Bound:约束泛型类型必须实现某些 Trait
  4. 生命周期:确保引用始终有效,避免悬垂引用
  5. 生命周期省略:编译器自动推断简单情况下的生命周期
  6. '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 测试的最佳实践

  1. 测试函数命名:使用描述性的名称说明测试内容

```rust #[test] fn it_handles_empty_input() { }

#[test] fn it_returns_none_for_invalid_value() { } ```

  1. 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);

} ```

  1. 为公开 API 编写文档测试,确保文档示例可运行

  2. 使用 #[should_panic] 测试边界条件

  3. 平衡单元测试和集成测试:单元测试验证细节,集成测试验证组件交互

11.13 总结

  1. #[test]:标记测试函数
  2. assert!:基本布尔断言
  3. assert_eq!:相等性断言
  4. assert_ne!:不等性断言
  5. #[should_panic]:预期 panic 的测试
  6. #[ignore]:忽略某些测试
  7. #[cfg(test)]:条件编译测试模块
  8. 单元测试:在源代码文件中测试单个模块
  9. 集成测试:在 tests 目录下测试组件交互
  10. cargo test:运行测试的命令
  11. --test-threads:控制并行或顺序运行
  12. --nocapture:显示 println! 输出
  13. --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 实现,并实现了 DerefDrop 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 会自动进行以下转换:

  1. &T&UT: Deref<Target=U>
  2. &mut T&mut UT: DerefMut<Target=U>
  3. &mut T&UT: 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,它是一个智能指针,实现了 DerefDerefMut
  • 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 的类型系统无法完全防止死锁,但遵循一些最佳实践可以避免大多数死锁情况。

避免死锁的原则

  1. 尽量缩短持锁时间:只在必要时持有锁
  2. 避免在持有锁时调用用户代码:用户代码可能再次获取锁
  3. 按固定顺序获取多个锁:如果需要多个锁,所有线程都按相同顺序获取

死锁示例(演示目的)

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 之上:SendSync。它们是 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 表明一个类型可以安全地被多个线程同时持有引用(即 &TSend 的)。如果 &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 的自动实现

大多数类型都会自动实现 SendSync

  • 如果 TSync,则 &TSend
  • 如果 T 的所有字段都实现了 Send,则 TSend
  • 如果 T 的所有字段都实现了 Sync,则 TSync

手动实现 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)和大多数并发错误,让开发者能够写出既安全又高效的并发代码。