=# Rust集合类型全解:Vector/HashMap/字符串,新手必学的3个核心结构
Rust的标准库集合类型,是日常开发中覆盖90%业务场景的核心工具——它们全部存储在堆内存中,无需在编译期确定长度,支持动态增删改查,完美解决了固定长度数组的局限性。

其中,动态数组Vec、字符串String/&str、哈希表HashMap<K,V> 是最基础、最常用的三个核心结构。很多新手写Rust代码踩坑,80%都来自对这三个类型的内存布局、所有权规则、API边界理解不到位。本文将从底层原理到常用API,再到新手必踩坑,最后通过完整实战带你吃透这三个集合类型,写出安全高效的Rust代码。


一、动态数组 Vec:有序序列的首选

Vec<T>是Rust的动态数组,和其他语言的数组/列表类似,用于存储相同类型、有序、可重复的元素,支持O(1)时间的索引访问和尾部增删,是Rust中最基础的集合类型。

1.1 核心内存布局

理解Vec的内存布局,是避免新手坑的关键。Vec本质上是一个包含三个字段的胖指针,存储在栈上,实际的元素数据存储在堆上:

// 简化的Vec底层结构
struct Vec<T> {
    ptr: *mut T,   // 裸指针,指向堆上存储元素的连续内存块
    len: usize,    // 当前已存储的元素个数
    capacity: usize, // 堆内存块总共能容纳的元素个数(预分配的容量)
}
  • len == capacity时,再执行push操作会触发扩容:Rust会申请一块更大的内存(默认扩容策略:容量小于1024时翻倍,大于等于1024时增长50%),将原有元素拷贝到新内存,释放旧内存,同时更新ptrcapacity
  • 扩容是新手坑的核心来源之一:扩容后原有堆内存地址改变,之前获取的元素引用会直接失效,编译器会强制报错。

1.2 高频常用方法(带实战示例)

1. 创建Vec
fn main() {
    // 1. 创建空Vec(推荐,初始容量为0,首次push自动扩容)
    let mut v1: Vec<i32> = Vec::new();

    // 2. 带预分配容量的创建(高频场景必用,避免多次扩容,提升性能)
    let mut v2: Vec<String> = Vec::with_capacity(100); // 预分配100个元素的空间

    // 3. 用vec!宏快速创建带初始值的Vec(最常用)
    let v3 = vec![1, 2, 3, 4, 5]; // 自动推断类型为Vec<i32>
    let v4 = vec![0; 10]; // 创建10个元素,每个都是0,等价于vec![0,0,...0]
}
2. 增:添加元素
fn main() {
    let mut v = Vec::new();

    // 1. 尾部添加单个元素(O(1) amortized)
    v.push(1);
    v.push(2);
    v.push(3); // v = [1,2,3]

    // 2. 指定位置插入元素(O(n),后面的元素会向后移动,少用!)
    v.insert(1, 10); // v = [1,10,2,3]

    // 3. 尾部批量添加元素(extend,比循环push高效)
    v.extend([4,5,6]); // v = [1,10,2,3,4,5,6]
    v.extend(vec![7,8]); // 支持任何实现了Iterator的类型
}
3. 删:删除元素
fn main() {
    let mut v = vec![1,2,3,4,5,6];

    // 1. 尾部弹出元素(O(1),返回Option<T>,空Vec返回None)
    let last = v.pop(); // last = Some(6), v = [1,2,3,4,5]

    // 2. 删除指定索引的元素(O(n),后面的元素向前移动,少用!)
    let removed = v.remove(1); // removed = 2, v = [1,3,4,5]

    // 3. 批量删除:保留符合条件的元素(retain,高效过滤)
    v.retain(|x| x % 2 == 1); // 只保留奇数,v = [1,3,5]

    // 4. 清空所有元素(保留容量)
    v.clear(); // v = [], capacity不变
}
4. 改:修改元素
fn main() {
    let mut v = vec![1,2,3,4,5];

    // 1. 索引直接修改(注意:索引越界会直接panic!)
    v[0] = 10; // v = [10,2,3,4,5]

    // 2. 遍历修改所有元素(iter_mut,可变借用,安全)
    for num in v.iter_mut() {
        *num *= 2; // 每个元素乘2,v = [20,4,6,8,10]
    }

    // 3. 安全修改指定位置元素(get_mut,返回Option,无panic风险)
    if let Some(num) = v.get_mut(2) {
        *num = 100; // 索引2存在,修改为100,v = [20,4,100,8,10]
    }
}
5. 查:访问元素&遍历
fn main() {
    let v = vec![1,2,3,4,5];

    // 1. 安全索引访问(get,返回Option<&T>,越界返回None,推荐)
    let first = v.get(0); // Some(&1)
    let out_of_bounds = v.get(10); // None,不会panic

    // 2. 直接索引访问([],越界直接panic,生产环境慎用!)
    let second = v[1]; // 2

    // 3. 判断元素是否存在
    let has_three = v.contains(&3); // true

    // 4. 不可变遍历(iter,只读,不转移所有权,最常用)
    for num in v.iter() {
        println!("{}", num);
    }

    // 5. 消费型遍历(into_iter,转移所有权,遍历后原Vec不可用)
    for num in v.into_iter() {
        println!("{}", num);
    }
    // 这里v已经被转移所有权,无法再使用!
}

1.3 新手必踩的5个坑&避坑指南

坑点 反面示例 正确做法
索引越界panic let x = v[100]; 数组长度不足直接崩溃 v.get(100),通过if let/ match处理None情况
扩容导致引用失效 let first = &v[0]; v.push(1); println!("{}", first); 不要在持有元素引用的同时执行push/insert等可能触发扩容的操作
into_iter转移所有权后复用原Vec for x in v.into_iter() {}; v.push(1); 编译报错 只读遍历用v.iter(),修改用v.iter_mut(),只有需要消费Vec时才用into_iter
频繁用remove删除头部元素 for _ in 0..1000 { v.remove(0); } 性能极差,每次都要移动所有元素 队列场景用std::collections::VecDeque,批量删除用retain,无需保持顺序用swap_remove
忽略预分配导致频繁扩容 循环10万次push,初始用Vec::new() 提前知道元素数量时,用Vec::with_capacity(n)预分配内存,减少扩容次数

二、字符串 String/&str:新手最容易踩坑的类型

Rust的字符串是新手入门的最大拦路虎之一,它和Python/Java等语言的字符串设计完全不同,核心根源是:Rust的字符串严格遵循UTF-8编码,且在编译期强制保证内存安全

2.1 核心概念与内存布局

Rust有两个核心字符串类型,必须彻底分清:

  1. &str:字符串切片,是对一段UTF-8编码字节序列的不可变借用,本质是胖指针(包含指向字节序列的指针+长度),没有所有权。
    • 字符串字面量"hello world"就是&'static str类型,存储在程序的静态只读内存中,生命周期贯穿整个程序运行周期。
  2. String:拥有所有权的、可增长的、可变的UTF-8字符串,本质是对Vec<u8>的封装,内存布局和Vec完全一致(栈上的胖指针+堆上的UTF-8字节数据)。

简单总结:String是你拥有的房子,可以随便装修改造;&str是房子的门牌号,只能用来访问,不能修改房子本身

2.2 高频常用方法(带实战示例)

1. String的创建
fn main() {
    // 1. 创建空String
    let mut s1 = String::new();

    // 2. 从字符串字面量创建(两种常用方式)
    let s2 = String::from("hello rust");
    let s3 = "hello rust".to_string();

    // 3. 格式化创建(最常用的拼接方式,无所有权转移)
    let name = "张三";
    let age = 20;
    let s4 = format!("姓名:{},年龄:{}", name, age); // 姓名:张三,年龄:20
}
2. String的修改(可变操作)
fn main() {
    let mut s = String::from("hello");

    // 1. 尾部追加字符串切片
    s.push_str(" world"); // s = "hello world"

    // 2. 尾部追加单个字符
    s.push('!'); // s = "hello world!"

    // 3. 插入字符串/字符(指定位置,注意:按字节索引,非字符!)
    s.insert(5, ", rust"); // s = "hello, rust world!"

    // 4. 替换内容
    s = s.replace("world", "Rust"); // s = "hello, rust Rust!"

    // 5. 弹出尾部字符
    let last = s.pop(); // last = Some('!')

    // 6. 清空字符串
    s.clear(); // s = ""
}
3. 字符串切片&str的常用方法
fn main() {
    let s = "  Hello Rust 你好  ";

    // 1. 获取字节长度(注意:不是字符数!UTF-8中中文占3个字节)
    let byte_len = s.len(); // 20,空格+英文+中文的总字节数

    // 2. 获取Unicode字符数(正确的"字符长度")
    let char_count = s.chars().count(); // 14,正确的字符个数

    // 3. 去除首尾空白
    let trimmed = s.trim(); // "Hello Rust 你好"

    // 4. 分割字符串
    let parts: Vec<&str> = trimmed.split_whitespace().collect(); // ["Hello", "Rust", "你好"]

    // 5. 判断前缀/后缀/包含
    let starts_with_hello = trimmed.starts_with("Hello"); // true
    let ends_with_hi = trimmed.ends_with("你好"); // true
    let contains_rust = trimmed.contains("Rust"); // true

    // 6. 遍历Unicode字符(唯一正确的遍历每个字符的方式)
    for c in trimmed.chars() {
        println!("{}", c);
    }

    // 7. 字符串转其他类型(parse,返回Result)
    let num_str = "123";
    let num: i32 = num_str.parse().unwrap(); // 123
}
4. 类型转换
fn main() {
    let s = String::from("hello");

    // 1. String 转 &str(自动解引用,直接用&即可,最常用)
    let s_slice: &str = &s;

    // 2. &str 转 String
    let s1: String = "hello".to_string();
    let s2: String = String::from("hello");

    // 3. String 转 字节数组Vec<u8>(消费所有权)
    let bytes: Vec<u8> = s.into_bytes();

    // 4. 字节数组 转 String(验证UTF-8合法性)
    let valid_bytes = vec![104, 101, 108, 108, 111];
    let s_from_bytes = String::from_utf8(valid_bytes).unwrap(); // "hello"

    // 处理非法UTF-8字节,用from_utf8_lossy,非法字符替换为�
    let invalid_bytes = vec![104, 101, 0xFF, 108, 111];
    let s_lossy = String::from_utf8_lossy(&invalid_bytes); // "he�lo"
}

2.3 新手必踩的6个坑&避坑指南

坑点 反面示例 正确做法
用len()获取中文字符长度,结果错误 "你好".len() == 2 实际返回6,因为每个中文占3个字节 获取字符数用.chars().count(),字节长度用.len()
用[]索引访问中文字符,编译报错/panic "你好"[0] 访问的是第一个字节0xE4,不是’你’,索引1会直接panic 访问第n个字符用.chars().nth(n),返回Option,安全无panic
字符串拼接转移所有权,原变量无法使用 let s3 = s1 + &s2; println!("{}", s1); 编译报错,s1所有权被转移 多字符串拼接优先用format!()宏,不会转移任何变量的所有权
函数参数用String而非&str,导致不必要的拷贝 fn print(s: String) { println!("{}", s); } 调用时必须传入拥有所有权的String 只读场景函数参数一律用&str,支持String和字符串字面量传入,无所有权转移
随意修改字节破坏UTF-8编码,导致乱码 let mut s = String::from("你好"); unsafe { s.as_mut_vec()[0] = 0xFF; } 直接乱码 不要直接修改字符串的底层字节,如需修改,通过chars()处理字符后重新构建String
混淆字符和字节的索引,导致split/insert出错 s.insert(2, 'x') 插入到"你好"的第2个字节,破坏UTF-8编码 所有涉及位置的操作,先通过chars()遍历确定字符的字节边界,再执行操作

三、哈希表 HashMap<K,V>:键值对快速查找的首选

HashMap<K,V>是基于哈希表实现的键值对集合,用于存储键值对映射,要求键必须唯一,且实现了EqHash trait,支持O(1)平均时间的增删改查,是处理映射关系、缓存、字典场景的核心结构。

3.1 核心原理

Rust标准库的HashMap默认使用SipHash哈希函数,能有效防止哈希碰撞攻击,同时保证不错的性能。核心特性:

  • 键必须唯一,插入重复键时,旧值会被覆盖;
  • 无序存储,遍历顺序和插入顺序无关(如需有序,可用BTreeMap);
  • 有容量和负载因子(默认0.9),当键值对数量超过容量*负载因子时,会触发扩容,重新哈希所有键值对。

3.2 高频常用方法(带实战示例)

1. 创建HashMap
use std::collections::HashMap;

fn main() {
    // 1. 创建空HashMap
    let mut map1: HashMap<String, i32> = HashMap::new();

    // 2. 带预分配容量的创建(提前知道数量时必用,避免多次扩容和重新哈希)
    let mut map2: HashMap<u32, String> = HashMap::with_capacity(100);

    // 3. 从迭代器创建(比如Vec转HashMap)
    let vec = vec![("a", 1), ("b", 2), ("c", 3)];
    let map3: HashMap<_, _> = vec.into_iter().collect();
}
2. 增:插入键值对
use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();

    // 1. 基础插入(键存在则覆盖旧值,返回旧值Option<V>)
    let old_value = scores.insert("张三".to_string(), 90); // old_value = None
    let old_value2 = scores.insert("张三".to_string(), 95); // old_value2 = Some(90),旧值被覆盖

    // 2. 高级插入:entry API(Rust HashMap的灵魂,仅一次哈希查找,高效无panic)
    // 键不存在则插入默认值,返回值的可变引用
    scores.entry("李四".to_string()).or_insert(80); // 李四不存在,插入80
    scores.entry("张三".to_string()).or_insert(0); // 张三已存在,不修改

    // 经典场景:统计单词出现次数
    let words = vec!["apple", "banana", "apple", "orange", "banana", "apple"];
    let mut count_map = HashMap::new();
    for word in words {
        *count_map.entry(word).or_insert(0) += 1;
    }
    // count_map = {"apple":3, "banana":2, "orange":1}
}
3. 删:删除键值对
use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::from([
        ("张三", 95),
        ("李四", 80),
        ("王五", 88),
    ]);

    // 1. 删除指定键,返回对应的值Option<V>
    let removed = scores.remove("李四"); // removed = Some(80)

    // 2. 批量删除:保留符合条件的键值对
    scores.retain(|_, score| *score >= 90); // 只保留90分以上的,只剩"张三"

    // 3. 清空所有键值对
    scores.clear();
}
4. 改:修改值
use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::from([
        ("张三", 95),
        ("李四", 80),
    ]);

    // 1. 覆盖修改(insert)
    scores.insert("李四", 85); // 李四的分数改为85

    // 2. 安全修改:get_mut(返回Option<&mut V>,键不存在返回None)
    if let Some(score) = scores.get_mut("张三") {
        *score += 5; // 张三的分数加5,变为100
    }

    // 3. 不存在则插入,存在则修改(entry API,最优雅)
    scores.entry("王五".to_string()).and_modify(|score| *score += 2).or_insert(88);
}
5. 查:访问值&遍历
use std::collections::HashMap;

fn main() {
    let scores = HashMap::from([
        ("张三", 95),
        ("李四", 80),
        ("王五", 88),
    ]);

    // 1. 安全访问:get(返回Option<&V>,键不存在返回None,推荐)
    let zhang_score = scores.get("张三"); // Some(&95)
    let zhao_score = scores.get("赵六"); // None,无panic

    // 2. 直接索引访问([],键不存在直接panic,生产环境慎用!)
    let li_score = scores["李四"]; // 80

    // 3. 判断键是否存在
    let has_wang = scores.contains_key("王五"); // true

    // 4. 不可变遍历(iter,只读,不转移所有权)
    for (name, score) in scores.iter() {
        println!("{}: {}", name, score);
    }

    // 5. 可变遍历(iter_mut,可修改值)
    let mut scores_mut = scores.clone();
    for (_, score) in scores_mut.iter_mut() {
        *score += 2; // 所有人分数加2
    }
}

3.3 新手必踩的5个坑&避坑指南

坑点 反面示例 正确做法
键不存在用[]索引,直接panic let score = scores["赵六"]; 键不存在直接崩溃 scores.get("赵六"),通过if let/match处理None情况
先contains_key再insert,两次哈希查找,性能差 if !map.contains_key(&k) { map.insert(k, v); } map.entry(k).or_insert(v),仅一次哈希查找,代码更简洁
用f64/f32作为键,出现无法查找的问题 f64当键,NaN不等于自身,插入后无法找到 绝对不要用浮点数作为HashMap的键,改用整数、字符串等实现了稳定Eq的类型
插入后复用原变量,所有权被转移 let name = "张三".to_string(); map.insert(name, 90); println!("{}", name); 编译报错 插入时如果不需要复用原变量,直接传入字面量;需要复用则传入引用(注意生命周期),或clone()
遍历过程中修改HashMap,导致逻辑错误/编译报错 遍历的时候执行remove/insert操作,触发借用规则报错 遍历的时候先收集需要修改/删除的键,遍历结束后再批量操作,避免遍历过程中修改HashMap

四、实战:学生成绩管理小工具(全集合类型落地)

我们通过一个完整的命令行学生成绩管理工具,把上面三个核心集合类型全部落地,同时用上所有安全API,避开新手坑。

4.1 需求说明

工具实现6个核心功能,覆盖所有集合类型的常用场景:

  1. 添加学生:录入学号(唯一)、姓名、各科成绩;
  2. 查询学生:通过学号查询学生的各科成绩、平均分、排名;
  3. 修改成绩:通过学号修改学生的指定科目成绩;
  4. 删除学生:通过学号删除学生信息;
  5. 班级统计:统计全班平均分、各科最高分/最低分、分数段分布;
  6. 列出所有学生:按平均分从高到低排序,输出所有学生信息。

4.2 数据结构设计(全集合类型应用)

use std::collections::HashMap;

/// 学生结构体
#[derive(Debug, Clone)]
struct Student {
    id: u32,             // 学号,唯一标识
    name: String,        // 姓名,String类型(拥有所有权)
    scores: HashMap<String, f32>, // 各科成绩,HashMap:键=科目名,值=分数
}

/// 班级成绩管理器
struct ScoreManager {
    // 核心存储:HashMap,键=学号,值=学生信息,O(1)时间增删改查
    students: HashMap<u32, Student>,
}

4.3 完整实现代码

use std::collections::HashMap;
use std::io;

#[derive(Debug, Clone)]
struct Student {
    id: u32,
    name: String,
    scores: HashMap<String, f32>,
}

impl Student {
    /// 计算学生的平均分
    fn average(&self) -> f32 {
        if self.scores.is_empty() {
            return 0.0;
        }
        let sum: f32 = self.scores.values().sum();
        sum / self.scores.len() as f32
    }
}

struct ScoreManager {
    students: HashMap<u32, Student>,
}

impl ScoreManager {
    /// 创建新的成绩管理器
    fn new() -> Self {
        Self {
            students: HashMap::new(),
        }
    }

    /// 1. 添加学生,返回是否成功(学号重复则失败)
    fn add_student(&mut self, student: Student) -> Result<(), String> {
        // entry API:学号不存在则插入,存在则返回错误
        if self.students.contains_key(&student.id) {
            return Err(format!("学号{}已存在", student.id));
        }
        self.students.insert(student.id, student);
        Ok(())
    }

    /// 2. 通过学号查询学生,返回Option(安全无panic)
    fn get_student(&self, id: u32) -> Option<&Student> {
        self.students.get(&id)
    }

    /// 3. 修改学生的指定科目成绩
    fn update_score(&mut self, id: u32, subject: &str, new_score: f32) -> Result<(), String> {
        // 安全获取可变引用,学号不存在返回错误
        let student = self.students.get_mut(&id)
            .ok_or(format!("学号{}不存在", id))?;
        
        // 修改成绩,科目不存在则自动添加
        student.scores.insert(subject.to_string(), new_score);
        Ok(())
    }

    /// 4. 删除学生
    fn delete_student(&mut self, id: u32) -> Result<(), String> {
        // remove返回Option,判断是否存在
        self.students.remove(&id)
            .ok_or(format!("学号{}不存在", id))?;
        Ok(())
    }

    /// 5. 班级统计
    fn class_statistics(&self) {
        if self.students.is_empty() {
            println!("暂无学生数据");
            return;
        }

        // 用Vec存储所有学生的平均分,用于计算班级平均分
        let all_averages: Vec<f32> = self.students
            .values()
            .map(|s| s.average())
            .collect();
        
        let class_average: f32 = all_averages.iter().sum::<f32>() / all_averages.len() as f32;
        let max_average = all_averages.iter().fold(f32::MIN, |a, &b| a.max(b));
        let min_average = all_averages.iter().fold(f32::MAX, |a, &b| a.min(b));

        // 用HashMap统计各科的最高分、最低分
        let mut subject_stats: HashMap<String, (f32, f32, f32)> = HashMap::new();
        for student in self.students.values() {
            for (subject, score) in &student.scores {
                let entry = subject_stats.entry(subject.clone()).or_insert((f32::MAX, f32::MIN, 0.0));
                entry.0 = entry.0.min(*score); // 最低分
                entry.1 = entry.1.max(*score); // 最高分
                entry.2 += *score; // 总分
            }
        }

        // 输出统计结果
        println!("===== 班级统计 =====");
        println!("学生总数:{}人", self.students.len());
        println!("班级平均分:{:.2}", class_average);
        println!("班级最高平均分:{:.2}", max_average);
        println!("班级最低平均分:{:.2}", min_average);
        println!("\n各科成绩统计:");
        for (subject, (min, max, sum)) in subject_stats {
            let avg = sum / self.students.len() as f32;
            println!("{}:最低分{:.2},最高分{:.2},平均分{:.2}", subject, min, max, avg);
        }
    }

    /// 6. 按平均分从高到低列出所有学生
    fn list_all_students(&self) {
        if self.students.is_empty() {
            println!("暂无学生数据");
            return;
        }

        // 把HashMap的学生引用转为Vec,用于排序(HashMap无序)
        let mut sorted_students: Vec<&Student> = self.students.values().collect();
        // 按平均分降序排序
        sorted_students.sort_by(|a, b| b.average().partial_cmp(&a.average()).unwrap());

        println!("===== 学生排名 =====");
        for (rank, student) in sorted_students.iter().enumerate() {
            println!("第{}名:学号{},姓名{},平均分{:.2}",
                rank + 1, student.id, student.name, student.average());
            println!("  各科成绩:{:?}", student.scores);
        }
    }

    /// 计算学生的班级排名
    fn get_student_rank(&self, id: u32) -> Option<usize> {
        let mut sorted_students: Vec<&Student> = self.students.values().collect();
        sorted_students.sort_by(|a, b| b.average().partial_cmp(&a.average()).unwrap());
        sorted_students.iter().position(|s| s.id == id).map(|p| p + 1)
    }
}

/// 辅助函数:读取用户输入
fn read_input() -> String {
    let mut input = String::new();
    io::stdin().read_line(&mut input).expect("读取输入失败");
    input.trim().to_string()
}

fn main() {
    let mut manager = ScoreManager::new();
    println!("===== 学生成绩管理系统 =====");

    loop {
        println!("\n请选择功能:");
        println!("1. 添加学生");
        println!("2. 查询学生");
        println!("3. 修改成绩");
        println!("4. 删除学生");
        println!("5. 班级统计");
        println!("6. 列出所有学生");
        println!("0. 退出系统");
        print!("请输入选项:");

        let choice = read_input();
        match choice.as_str() {
            "1" => {
                // 添加学生
                println!("请输入学号:");
                let id: u32 = match read_input().parse() {
                    Ok(n) => n,
                    Err(_) => {
                        println!("学号必须是数字!");
                        continue;
                    }
                };

                println!("请输入姓名:");
                let name = read_input();

                let mut scores = HashMap::new();
                println!("请输入科目数量:");
                let subject_count: usize = match read_input().parse() {
                    Ok(n) => n,
                    Err(_) => {
                        println!("科目数量必须是数字!");
                        continue;
                    }
                };

                for i in 0..subject_count {
                    println!("请输入第{}个科目名称:", i + 1);
                    let subject = read_input();
                    println!("请输入{}的分数:", subject);
                    let score: f32 = match read_input().parse() {
                        Ok(s) => s,
                        Err(_) => {
                            println!("分数必须是数字!");
                            continue;
                        }
                    };
                    scores.insert(subject, score);
                }

                let student = Student { id, name, scores };
                match manager.add_student(student) {
                    Ok(_) => println!("添加成功!"),
                    Err(e) => println!("添加失败:{}", e),
                }
            }
            "2" => {
                // 查询学生
                println!("请输入要查询的学号:");
                let id: u32 = match read_input().parse() {
                    Ok(n) => n,
                    Err(_) => {
                        println!("学号必须是数字!");
                        continue;
                    }
                };

                match manager.get_student(id) {
                    Some(student) => {
                        println!("\n===== 学生信息 =====");
                        println!("学号:{}", student.id);
                        println!("姓名:{}", student.name);
                        println!("各科成绩:{:?}", student.scores);
                        println!("平均分:{:.2}", student.average());
                        if let Some(rank) = manager.get_student_rank(id) {
                            println!("班级排名:第{}名", rank);
                        }
                    }
                    None => println!("学号{}不存在", id),
                }
            }
            "3" => {
                // 修改成绩
                println!("请输入学生学号:");
                let id: u32 = match read_input().parse() {
                    Ok(n) => n,
                    Err(_) => {
                        println!("学号必须是数字!");
                        continue;
                    }
                };

                println!("请输入要修改的科目名称:");
                let subject = read_input();
                println!("请输入新的分数:");
                let new_score: f32 = match read_input().parse() {
                    Ok(s) => s,
                    Err(_) => {
                        println!("分数必须是数字!");
                        continue;
                    }
                };

                match manager.update_score(id, &subject, new_score) {
                    Ok(_) => println!("修改成功!"),
                    Err(e) => println!("修改失败:{}", e),
                }
            }
            "4" => {
                // 删除学生
                println!("请输入要删除的学号:");
                let id: u32 = match read_input().parse() {
                    Ok(n) => n,
                    Err(_) => {
                        println!("学号必须是数字!");
                        continue;
                    }
                };

                match manager.delete_student(id) {
                    Ok(_) => println!("删除成功!"),
                    Err(e) => println!("删除失败:{}", e),
                }
            }
            "5" => {
                // 班级统计
                manager.class_statistics();
            }
            "6" => {
                // 列出所有学生
                manager.list_all_students();
            }
            "0" => {
                println!("退出系统,再见!");
                break;
            }
            _ => {
                println!("无效选项,请重新输入!");
            }
        }
    }
}

4.4 代码核心亮点

  1. 全集合类型覆盖
    • HashMap<u32, Student>:核心存储,实现学号的快速增删改查;
    • HashMap<String, f32>:学生各科成绩,科目名到分数的映射;
    • Vec<&Student>:用于排序和统计,实现排名功能;
    • String/&str:姓名、科目、用户输入的处理,全程安全无乱码。
  2. 零panic风险:所有可能出错的场景都用Result/Option处理,没有无保障的unwrap()和索引访问;
  3. 高效API使用:全程用entry APIget/get_mut等高效安全的方法,避免多次哈希查找和不必要的拷贝;
  4. 所有权规范:只读场景用借用,需要修改用可变借用,避免不必要的所有权转移和内存拷贝。

五、总结:三个集合的使用场景选择

集合类型 核心优势 适用场景
Vec 有序、O(1)索引访问、尾部增删高效 列表、栈、队列、需要有序存储的同类型元素
String/&str 严格UTF-8安全、高效的字符串处理 所有文本处理场景,String用于拥有和修改,&str用于只读借用
HashMap<K,V> O(1)平均时间的键值对查找 唯一键映射、缓存、字典、需要快速通过键查找值的场景

对于Rust新手来说,吃透这三个集合类型,就掌握了Rust日常开发的核心数据结构。记住一个核心原则:永远优先使用安全的API(get/entry/iter等),避免会导致panic的直接索引和unwrap(),同时时刻关注所有权和借用规则,就能避开90%的坑,写出安全高效的Rust代码。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐