一、常用集合

Rust 标准库提供了多种集合类型,用于存储和组织数据。这些集合在堆上分配内存,因此它们的大小可以动态变化。

1. Vec 动态数组

Vec<T> 是一个可动态增长的数组,存储相同类型的元素。

// 创建 Vec 的几种方式
fn main() {
    // 1. 使用 Vec::new() 创建空向量
    let mut v1: Vec<i32> = Vec::new();
    
    // 2. 使用 vec! 宏创建并初始化
    let v2: Vec<i32> = vec![1, 2, 3, 4, 5];
    
    // 3. 使用 push 方法添加元素
    v1.push(10);
    v1.push(20);
    v1.push(30);
    
    // 访问元素
    println!("v2 的第二个元素: {}", v2[1]);
    
    // 使用 get 方法安全访问(返回 Option<T>)
    match v2.get(10) {
        Some(value) => println!("元素值: {}", value),
        None => println!("索引超出范围"),
    }
}

2. HashMap<K, V> 哈希表

HashMap<K, V> 存储键值对,提供快速的查找能力。需要从 std::collections 导入。

use std::collections::HashMap;
fn main() {
    // 创建 HashMap
    let mut scores = HashMap::new();
    
    // 插入键值对
    scores.insert(String::from("Alice"), 90);
    scores.insert(String::from("Bob"), 85);
    scores.insert(String::from("Charlie"), 95);
    
    // 访问值
    let name = String::from("Alice");
    if let Some(score) = scores.get(&name) {
        // 访问成功,则执行下面的代码
        println!("Alice 的分数: {}", score);
    }

    let name = String::from("Tom");
    //安全访问
    match scores.get(&name) {
        Some(x) => {
            println!("找到了{}的分数:{}",&name,x);
        },
        None => {
            println!("没找到{}的分数",&name);
        },
    }

    // 遍历 HashMap
    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }
    
    // 插入或更新值
    scores.entry(String::from("Bob")).or_insert(80); // Bob 已有值,不会更新
    scores.entry(String::from("Dave")).or_insert(75); // Dave 不存在,插入新值
}

3. HashSet 哈希集合

HashSet<T> 存储唯一值,不包含重复元素。同样需要从 std::collections 导入。

use std::collections::HashSet;

fn main() {
    // 创建 HashSet
    let mut fruits = HashSet::new();
    
    // 插入元素
    fruits.insert(String::from("苹果"));
    fruits.insert(String::from("香蕉"));
    fruits.insert(String::from("橙子"));
    
    // 尝试插入重复元素(会被忽略)
    let result = fruits.insert(String::from("苹果"));
    println!("苹果是否插入成功: {}", result); // 输出 false
    
    // 检查元素是否存在
    if fruits.contains("香蕉") {
        println!("集合中包含香蕉");
    }
    
    // 遍历集合
    for fruit in &fruits {
        println!("水果: {}", fruit);
    }
}

二、集合的迭代与修改

1. 增删改查

use std::collections::{HashMap, HashSet};

fn main() {
    // Vec 操作
    let mut numbers = vec![1, 2, 3];
    numbers.push(4); // 增
    numbers[1] = 20; // 改
    numbers.remove(0); // 删
    println!("Vec: {:?}", numbers);
    
    // HashMap 操作
    let mut map = HashMap::new();
    map.insert(1, "one"); // 增
    map.insert(1, "first"); // 改(覆盖)
    map.remove(&1); // 删
    println!("HashMap: {:?}", map);
    
    // HashSet 操作
    let mut set = HashSet::new();
    set.insert(1); // 增
    set.remove(&1); // 删
    println!("HashSet: {:?}", set);
}

2. 排序

fn main() {
    let mut numbers = vec![3, 1, 4, 1, 5, 9, 2, 6];
    
    // 升序排序
    numbers.sort();
    println!("升序排序: {:?}", numbers);
    
    // 降序排序
    numbers.sort_by(|a, b| b.cmp(a));
    println!("降序排序: {:?}", numbers);
    
    // 字符串排序
    let mut words = vec!["banana", "apple", "cherry"];
    words.sort();
    println!("字符串排序: {:?}", words);
}

3. 过滤与转换

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // 过滤偶数
    let even_numbers: Vec<i32> = numbers.iter()
        .filter(|&&x| x % 2 == 0)
        .cloned()
        .collect();
    println!("偶数: {:?}", even_numbers);
    
    // 转换为平方数
    let squares: Vec<i32> = numbers.iter()
        .map(|&x| x * x)
        .collect();
    println!("平方数: {:?}", squares);
    
    // 过滤并转换
    let even_squares: Vec<i32> = numbers.iter()
        .filter(|&&x| x % 2 == 0)
        .map(|&x| x * x)
        .collect();
    println!("偶数的平方: {:?}", even_squares);
}

三、错误处理模型

Rust 有两种主要的错误处理方式:用于不可恢复错误的 panic! 和用于可恢复错误的 Result 枚举。

1. 不可恢复错误(panic!)

panic! 宏会导致程序立即终止并展开调用栈,通常用于处理无法恢复的错误。

fn main() {
    // 直接调用 panic!
    // panic!("发生了严重错误!");
    
    // 在调试中使用 assert! 系列宏
    let value = 5;
    assert_eq!(value, 5, "value 应该等于 5"); // 断言成功
    // assert_ne!(value, 5, "value 不应该等于 5"); // 断言失败,会 panic
    
    // 访问越界索引会导致 panic
    let v = vec![1, 2, 3];
    // println!("{}", v[10]); // 会 panic
}

2. 可恢复错误(Result)

Result<T, E> 枚举用于处理可能恢复的错误,它有两个变体:

  • Ok(T):表示操作成功,包含成功时的值
  • Err(E):表示操作失败,包含错误信息
use std::fs::File;
use std::io::Read;

fn main() {
    // 尝试打开文件
    let file_result = File::open("example.txt");
    
    let mut file = match file_result {
        Ok(f) => f,
        Err(e) => {
            println!("无法打开文件: {}", e);
            return;
        }
    };
    
    // 读取文件内容
    let mut content = String::new();
    let read_result = file.read_to_string(&mut content);
    
    match read_result {
        Ok(_) => println!("文件内容: {}", content),
        Err(e) => println!("无法读取文件: {}", e),
    }
}

四、错误传播

当函数遇到错误但不想立即处理时,可以将错误传播给调用者。

1. ? 运算符

? 运算符简化了错误传播的过程,它会自动将 Result 进行匹配:

  • 如果是 Ok(t),则解包并返回 t
  • 如果是 Err(e),则返回 Err(e)
use std::fs::File;
use std::io::{self, Read};

// 使用 ? 运算符传播错误
fn read_file_content(filename: &str) -> Result<String, io::Error> {
    let mut file = File::open(filename)?; // 传播可能的错误
    let mut content = String::new();
    file.read_to_string(&mut content)?; // 传播可能的错误
    Ok(content)
}

fn main() {
    match read_file_content("example.txt") {
        Ok(content) => println!("文件内容: {}", content),
        Err(e) => println!("读取文件时出错: {}", e),
    }
}

2. 自定义错误类型

在实际开发中,我们经常需要定义自己的错误类型来更好地表示应用程序特有的错误。

use std::fmt;
use std::fs::File;
use std::io::{self, Read};

// 自定义错误类型
#[derive(Debug)]
enum MyError {
    IoError(io::Error),
    ParseError(String),
    CustomError(String),
}

// 实现 Display trait 以便打印错误信息
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MyError::IoError(e) => write!(f, "IO 错误: {}", e),
            MyError::ParseError(msg) => write!(f, "解析错误: {}", msg),
            MyError::CustomError(msg) => write!(f, "自定义错误: {}", msg),
        }
    }
}

// 实现 Error trait
impl std::error::Error for MyError {}

// 从 io::Error 转换为 MyError
impl From<io::Error> for MyError {
    fn from(err: io::Error) -> Self {
        MyError::IoError(err)
    }
}

// 使用自定义错误类型的函数
fn read_and_parse_file(filename: &str) -> Result<i32, MyError> {
    let mut file = File::open(filename)?; // 这里会自动转换为 MyError::IoError
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    
    // 尝试解析为整数
    content.trim().parse().map_err(|_| {
        MyError::ParseError(format!("无法将 '{}' 解析为整数", content))
    })
}

fn main() {
    match read_and_parse_file("number.txt") {
        Ok(num) => println!("解析成功: {}", num),
        Err(e) => println!("发生错误: {}", e),
    }
}

要点:

  1. 定义枚举类型(如MyError),包含不同错误变体(包装具体错误或信息),并派生Debug
  2. 实现Display trait,定义各错误变体的用户友好描述。
  3. 实现std::error::Error trait(空实现即可),使其符合标准错误规范。
  4. 实现From trait(针对需兼容的标准错误,如io::Error),支持错误自动转换。
  5. 函数返回Result<T, 自定义错误>,通过?传播自动转换的错误,或用map_err手动转换其他错误。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐