Rust基础[part8]_模式匹配、常见集合

Rust基础[part8]_模式匹配、常见集合

模式匹配

检查数据结构,提高代码的可读性和简洁性,减少错误,尤其在处理复杂数据结构的时候

基础模式匹配

fn match_example() {
    let x = 5;
    // 必须要有所有可能的case
    match x {
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        _ => println!("something else"),
    }
}

守卫

在模式匹配中,可以总使用守卫来添加额外的条件判断。

let x = 5;
match x {
    n if n > 5 => println!("x is greater than 5"),
    _ => println!("x is less than or equal to 5"),
}

绑定

在模式匹配中,可以使用绑定来将模式中的值绑定到变量上

@ 0..=5这个是用来指定范围的

// 绑定:可以把匹配到的值绑定到一个变量上
    let enum1 = MyEnum::Hello { a: 5 };
    match enum1 {
        MyEnum::Hello { a: val @ 0..=5 } => println!("x is {}", val),
        MyEnum::Hello { a: val @ 5.. } => println!("x is {}", val),
        MyEnum::B(s) => println!("s is {}", s),
        MyEnum::C => println!("C"),
        _ => println!("something else"),
    }

应用场景

  • 处理错误

    /**
     * 匹配模式应用场景
     */
    fn pattern_match_example() { 
        // 1 匹配错误
        match divide(3, 2) {
            Ok(result) => println!("Result: {}", result),
            Err(error) => println!("Error: {}", error),
        }
    }
    
    fn divide(a: i32, b: i32) -> Result<i32, String> {
        if b == 0 {
            return Err("Division by zero is not allowed".to_string());
        } else {
            Ok(a / b)
        }
    }
    
  • 解析命令行参数

  • 解析配置文件

  • 解析数据包

  • 解析XML或JSON

高级匹配技巧

嵌套模式

这里 Message::Move { x, y } 是一个嵌套模式,因为它:

  • 匹配外层枚举变体 Message::Move
  • 同时解构了内部的结构体字段 { x: i32, y: i32 }

这样你就可以在一个步骤中直接访问到 xy

enum Message {
      Quit,
      Move { x: i32, y: i32 },
      Write(String),
      ChangeColor(i32, i32, i32),
  }
  let msg = Message::Move { x: 10, y: 20 };

  match msg {
      Message::Quit => println!("Quit"),
      Message::Move { x, y } => println!("Move to ({}, {})", x, y),
      Message::Write(text) => println!("Write: {}", text),
      Message::ChangeColor(r, g, b) => println!("Change color to ({}, {}, {})", r, g, b),// 可以使用enum中的参数
  }

匹配模式和迭代器:结合iter和match使用

  • for (a, b) 中的括号结构本质上是一种模式匹配语法
  • 它和 match 一样可以解构元组、结构体、枚举等复杂类型
  • iter() 提供了迭代器的能力,match(或模式匹配)提供了结构解构的能力
  • 所以这段代码体现了 itermatch 的结合使用
fn iterator_match_example() { 
    let vec1 = vec![1, 2, 3];
    let vec2 = vec![4, 5, 6];
    for (a, b) in vec1.iter().zip(vec2)  {
        println!("{} + {} = {}", a, b, a+b);
    }
}

if let 和while let: 简化单个模式匹配

fn if_let_example() {
    let option = Some(5);
    if let Some(x) = option {
        println!("{}", x);
    }
}

fn while_let_example() {
    let mut vec = vec![1, 2, 3];
    while let Some(x) = vec.pop() {
        println!("{}", x);
    }
}

ref和ref mut

  • 借用数据而不转移所有权:在某些情况下,你只需要借用数据而不是转移所有权。例如在递归数据结构中,借用数据可以避免所有权转移带来的复杂性
  • 对数据进行修改:使用ref mut 可以在模式匹配时对对数据进行修改,而无需转移所有权
fn ref_example() {
    let x = 5;
    match x {
        ref var => println!("{}", var),
        _ => {}
    }
}

fn ref_mut_example() {
    let mut x = 5;
    match x {
        ref mut var => {
            *var += 1;
        }
        _ => {}
    }
}

练习

目标

  • 理解如何使用 Rust 的模式匹配功能解析 JSON 数据。
  • 学会使用 serde_json 库进行 JSON 处理。
  • 练习在实际应用场景中使用模式匹配。

要求

  • 使用 serde_json 库解析 JSON 字符串。
  • 使用模式匹配提取 JSON 对象中的不同字段。
  • 处理不同类型的数据(字符串、数字、数组、嵌套对象等)。

示例

假设你有一个包含用户信息的 JSON 字符串:

{
  "name": "Alice",
  "age": 30,
  "email": "alice@example.com",
  "address": {
    "street": "123 Main St",
    "city": "Wonderland"
  },
  "phone_numbers": ["123-456-7890", "987-654-3210"]
}

答案:

use serde_json::{Result, Value};
use std::fmt;

// 定义错误类型(替代简单的 String 错误)
#[derive(Debug)]
enum ParseError {
    Json(serde_json::Error),
    FieldMissing(&'static str),
    TypeMismatch(&'static str, &'static str), // 字段名 + 期望类型
    NumberRange(&'static str), // 数字超出范围
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ParseError::Json(e) => write!(f, "JSON 解析错误: {e}"),
            ParseError::FieldMissing(field) => write!(f, "字段缺失: {field}"),
            ParseError::TypeMismatch(field, expect) => {
                write!(f, "字段 {field} 类型错误,期望 {expect}")
            }
            ParseError::NumberRange(field) => write!(f, "字段 {field} 数值超出范围"),
        }
    }
}

impl From<serde_json::Error> for ParseError {
    fn from(e: serde_json::Error) -> Self {
        ParseError::Json(e)
    }
}

#[derive(Debug)]
struct Address {
    street: String,
    city: String,
}

#[derive(Debug)]
struct Info {
    name: String,
    age: u32,
    email: String,
    address: Address,
    phone_numbers: Vec<String>,
}

// 解析函数:使用模式匹配提取字段,返回 Result 处理错误
fn parse_json(json: &str) -> Result<Info, ParseError> {
    let value: Value = serde_json::from_str(json)?;

    // 1. 提取 name(字符串类型)
    let name = match value.get("name") {
        Some(Value::String(s)) => s.to_string(),
        Some(_) => return Err(ParseError::TypeMismatch("name", "字符串")),
        None => return Err(ParseError::FieldMissing("name")),
    };

    // 2. 提取 age(非负整数,且在 u32 范围内)
    let age = match value.get("age") {
        Some(Value::Number(n)) => {
            n.as_u64()
                .ok_or(ParseError::TypeMismatch("age", "非负整数"))?
                .try_into()
                .map_err(|_| ParseError::NumberRange("age"))?
        }
        Some(_) => return Err(ParseError::TypeMismatch("age", "数字")),
        None => return Err(ParseError::FieldMissing("age")),
    };

    // 3. 提取 email(字符串类型)
    let email = match value.get("email") {
        Some(Value::String(s)) => s.to_string(),
        Some(_) => return Err(ParseError::TypeMismatch("email", "字符串")),
        None => return Err(ParseError::FieldMissing("email")),
    };

    // 4. 提取嵌套对象 address
    let address = match value.get("address") {
        Some(Value::Object(obj)) => {
            let street = match obj.get("street") {
                Some(Value::String(s)) => s.to_string(),
                Some(_) => return Err(ParseError::TypeMismatch("address.street", "字符串")),
                None => return Err(ParseError::FieldMissing("address.street")),
            };
            let city = match obj.get("city") {
                Some(Value::String(s)) => s.to_string(),
                Some(_) => return Err(ParseError::TypeMismatch("address.city", "字符串")),
                None => return Err(ParseError::FieldMissing("address.city")),
            };
            Address { street, city }
        }
        Some(_) => return Err(ParseError::TypeMismatch("address", "对象")),
        None => return Err(ParseError::FieldMissing("address")),
    };

    // 5. 提取数组 phone_numbers(元素为字符串)
    let phone_numbers = match value.get("phone_numbers") {
        Some(Value::Array(arr)) => {
            arr.iter()
                .map(|elem| match elem {
                    Value::String(s) => Ok(s.to_string()),
                    _ => Err(ParseError::TypeMismatch("phone_numbers 元素", "字符串")),
                })
                .collect::<Result<Vec<_>, _>>()?
        }
        Some(_) => return Err(ParseError::TypeMismatch("phone_numbers", "数组")),
        None => return Err(ParseError::FieldMissing("phone_numbers")),
    };

    Ok(Info {
        name,
        age,
        email,
        address,
        phone_numbers,
    })
}

fn main() {
    let json_str = r#"
    {
        "name": "Alice",
        "age": 30,
        "email": "alice@example.com",
        "address": {
            "street": "123 Main St",
            "city": "Wonderland"
        },
        "phone_numbers": ["123-456-7890", "987-654-3210"]
    }
    "#;

    match parse_json(json_str) {
        Ok(info) => println!("解析结果:\n{:#?}", info),
        Err(e) => eprintln!("解析失败: {e}"),
    }
}

常见集合

Vec

基本用法

  • 创建和初始化
  • 添加元素
  • 访问元素
  • 修改元素
  • 遍历元素
// 创建一个空的vec
    let mut v: Vec<i32> = Vec::new();
    // 使用宏来创建一个veck k
    let mut v1: Vec<i32> = vec![1, 2, 3];

    // 添加元素
    v.push(5);

    // 访问元素
    // 1.使用索引
    let third: &i32 = &v[2];
    println!("The third element is {}", third);
    // 2.使用get方法
    match v.get(2) {
        Some(third) => println!("The third element is {}", third),
        None => println!("There is no third element."),
    }

    // 修改元素
    v[0] = 4;

    // 迭代元素
    for i in &v {
        println!("{}", i);
    }

高阶用法:

// 进阶的用法
  // 1.使用枚举来存储多种类型
  enum SpreadsheetCell {
      Int(i32),
      Float(f64),
      Text(String),
  }

  let row = vec![
      SpreadsheetCell::Int(3),
      SpreadsheetCell::Text(String::from("blue")),
      SpreadsheetCell::Float(10.12),
  ];

  // 2.容量与重新分配
  let mut v = Vec::with_capacity(10);
  v.push(1);
  println!("{}", v.capacity());

常见错误:

  • 不安全的索引访问,所以最好使用match

    // 不安全的索引访问
        let v2 = vec![1, 2, 3, 4, 5];
        // println!("{}", v2[100]); // 运行时会出错
    
  • 可变引用与不可变引用的混用

    
    // 可变引用和不可变引用混用
        let mut v3 = vec![1, 2, 3, 4, 5];
        let first = &v3[0];
        v3.push(6); // 这里会报错
        println!("{}", first);
    
    ---------------
    cannot borrow `v3` as mutable because it is also borrowed as immutable
    mutable borrow occurs hererustcClick for full compiler diagnostic
    

    修复的话可以分两种,可以修改push的顺序,或者使用作用域的特性来控制引用的生命周期

    // 第一种
    v3.push(6);
    let first: &i32 = &v3[0];
    
    // 第二种
    {
            let first: &i32 = &v3[0];
            println!("{}", first);
        }
        v3.push(6);

HashMap

基本操作

// 基本操作
  let mut scores: HashMap<String, i32> = HashMap::new();
  scores.insert(String::from("Blue"), 10);
  scores.insert(String::from("Yellow"), 50);

  // 获取元素
  let team_name = String::from("Blue");
  let score: Option<&i32> = scores.get(&team_name);

  match score {
      Some(score) => println!("{}", score),
      None => println!("None"),
  }

  // 遍历
  for (key, value) in &scores {
      println!("{} : {}", key, value);
  }

进阶操作

  • 更新哈希表

    // 更新
       scores.insert(String::from("Blue"), 25);
       scores.entry(String::from("Blue")).or_insert(50);
      
  • 合并哈希表

    // 合并两个集合
      let mut map1 = HashMap::new();
      map1.insert(1, "one");
      let mut map2 = HashMap::new();
      map2.insert(2, "two");
      map2.insert(3, "three");
      for (k, v) in &map2 {
          map1.entry(*k).or_insert(&v);
      }
      println!("{:?}", map1);

常见陷阱

  • 哈希冲突

  • 所有权问题

fn hashmap_ownership() {
    let mut scores = HashMap::new();
    let team_name = String::from("Blue");
    let team_score = 10;
    scores.insert(team_name, team_score);
    println!("{:?}", scores);

    println!("{}", team_name); // 这里会报错

修改为.clone()

scores.insert(team_name.clone(), team_score.clone());

练习

  1. 使用Vec实现一个简单的栈
struct Stack<T> {
    elements: Vec<T>,
}

impl<T> Stack<T> {
    // 初始化空栈
    fn new() -> Self {
        Stack {
            elements: Vec::new(),
        }
    }

    // 入栈:向尾部添加元素
    fn push(&mut self, item: T) {
        self.elements.push(item);
    }

    // 出栈:移除并返回尾部元素(空栈时返回 None)
    fn pop(&mut self) -> Option<T> {
        self.elements.pop()
    }

    // 查看栈顶:返回尾部元素的引用(空栈时返回 None)
    fn peek(&self) -> Option<&T> {
        self.elements.last()
    }
}
#[test]
// 测试用例
fn main() {
    let mut stack = Stack::new();
    stack.push(1);
    stack.push(2);
    stack.push(3);

    println!("栈顶元素(peek): {:?}", stack.peek()); // 输出: Some(3)
    println!("出栈元素(pop): {:?}", stack.pop()); // 输出: Some(3)
    println!("出栈后栈顶: {:?}", stack.peek()); // 输出: Some(2)
}
  1. 使用HashMap实现字频统计器
/**
 * 通过hashmap计算单词频率
 */

fn count_frequency(s: &str) -> HashMap<&str, i32> {
    let mut letters: HashMap<&str, i32> = HashMap::new();
    for ch in s.split_whitespace() {
        letters
            .entry(ch)
            .and_modify(|counter| *counter += 1)
            .or_insert(1);
    }
    return letters;
}

#[test]
fn test_count_frequency() {
    let s = "hello world hello rust";
    let letters = count_frequency(s);
    println!("{:?}", letters);
}
  1. 使用Vec和HashMap实现一个简单的书籍库存管理系统:
// 库存管理系统
struct InventorySystem {
    books: Vec<Book>,                 // 存储所有书籍
    id_to_index: HashMap<u32, usize>, // ID -> Vec索引的映射
    next_id: u32,                     // 下一个可用ID(自增)
}

impl InventorySystem {
    // 初始化空系统
    fn new() -> Self {
        InventorySystem {
            books: Vec::new(),
            id_to_index: HashMap::new(),
            next_id: 1, // ID从1开始
        }
    }

    // 添加书籍:自动分配ID,返回新书籍ID
    fn add_book(&mut self, title: String, author: String, quantity: u32) -> u32 {
        let id = self.next_id;
        self.next_id += 1;

        let book = Book {
            id,
            title,
            author,
            quantity,
        };
        let index = self.books.len();
        self.books.push(book);
        self.id_to_index.insert(id, index); // 记录ID与索引的映射

        id
    }

    // 按ID查询书籍:返回Option<&Book>(不存在则返回None)
    fn get_book(&self, id: u32) -> Option<&Book> {
        self.id_to_index.get(&id).map(|&index| &self.books[index]) // 通过索引取书籍引用
    }

    // 按标题模糊查询:返回所有包含该标题的书籍
    fn search_by_title(&self, title: &str) -> Vec<&Book> {
        self.books
            .iter()
            .filter(|book| book.title.contains(title))
            .collect()
    }

    // 更新库存:按ID修改数量,返回是否成功
    fn update_quantity(&mut self, id: u32, new_quantity: u32) -> bool {
        if let Some(&index) = self.id_to_index.get(&id) {
            self.books[index].quantity = new_quantity;
            true
        } else {
            false
        }
    }

    // 删除书籍:按ID删除,返回是否成功
    // (注:删除时将最后一本书移到被删位置,保证Vec索引一致性)
    fn remove_book(&mut self, id: u32) -> bool {
        if let Some(index) = self.id_to_index.remove(&id) {
            // 若删除的不是最后一本书,将最后一本书移到删除位置
            if index < self.books.len() - 1 {
                let last_book = self.books.pop().unwrap(); // 取出最后一本书
                self.books[index] = last_book.clone(); // 覆盖到删除位置
                self.id_to_index.insert(last_book.id, index); // 更新最后一本书的索引映射
            } else {
                self.books.pop(); // 删除最后一本书,无需调整映射
            }
            true
        } else {
            false
        }
    }
}

// 测试用例
#[test]
fn test_inventory_system() {
    let mut inventory = InventorySystem::new();

    // 添加书籍
    let id1 = inventory.add_book("Rust编程入门".to_string(), "张三".to_string(), 10);
    let id2 = inventory.add_book("Effective Rust".to_string(), "李四".to_string(), 5);

    // 查询书籍
    println!("书籍1: {}", inventory.get_book(id1).unwrap());
    println!("书籍2: {}", inventory.get_book(id2).unwrap());

    // 更新库存
    inventory.update_quantity(id1, 20);
    println!("更新后书籍1: {}", inventory.get_book(id1).unwrap());

    // 模糊查询
    let results = inventory.search_by_title("Rust");
    println!("含'Rust'的书籍:");
    for book in results {
        println!("{}", book);
    }

    // 删除书籍
    inventory.remove_book(id2);
    println!("删除书籍2后,查询是否存在: {:?}", inventory.get_book(id2));
}