Rust 集合与字符串(详细教程)

Rust 集合与字符串:从入门到实用

在学习 Rust 的过程中,集合与字符串是两个最常接触、也最容易被低估的核心模块。它们不仅仅是存储数据的容器,更是 Rust 内存安全与性能设计哲学的集中体现。如果你已经掌握了变量、函数和基本类型,那么现在正是深入理解“Rust 集合与字符串”的好时机。

Rust 的集合类型(如 Vec、HashMap、HashSet)和字符串处理机制(String、&str)在设计上兼顾了效率与安全。例如,Rust 不允许空指针,也不允许数据竞争,而这一切都建立在对集合与字符串的精细控制之上。本文将带你一步步掌握这些核心概念,结合实际案例,让你真正“用得上、用得对、用得安心”。


可变数组:Vec 的灵活运用

在 Rust 中,最常用的动态数组是 Vec<T>,它相当于其他语言中的“动态数组”或“列表”。它的名字来源于“vector”(向量),意味着它能自动扩容,也支持高效的随机访问。

创建数组与初始化

// 创建一个空的整数向量
let mut numbers = Vec::new();

// 通过宏直接初始化,类型会自动推导
let fruits = vec!["苹果", "香蕉", "橙子"];

// 显式指定类型,更清晰
let scores: Vec<i32> = vec![85, 92, 78, 96];

注释:vec![] 是 Rust 提供的宏,用于快速创建 Vec<T>let mut 表示该变量可变,允许后续添加或修改元素。

常用操作:增删改查

let mut colors = vec!["红色", "绿色"];

// 添加元素到末尾
colors.push("蓝色");

// 访问元素(索引从 0 开始)
println!("第一个颜色是:{}", colors[0]);

// 使用 get 方法安全访问(返回 Option)
if let Some(color) = colors.get(1) {
    println!("第二个颜色是:{}", color);
} else {
    println!("索引超出范围");
}

// 移除最后一个元素
let last_color = colors.pop();
println!("移除了:{:?}", last_color);

// 修改某个元素
colors[0] = "紫色";

注释:push 是添加元素的高效方式,时间复杂度 O(1);get 返回 Option<&T>,避免越界访问;pop 返回 Option<T>,如果向量为空则返回 None


哈希集合与映射:高效查找的利器

当你需要快速判断某个值是否存在,或者根据键查找值时,HashSetHashMap 就派上用场了。它们底层使用哈希表实现,平均查找时间复杂度为 O(1),性能极佳。

HashSet:不重复的集合

use std::collections::HashSet;

let mut unique_numbers = HashSet::new();

// 添加元素
unique_numbers.insert(10);
unique_numbers.insert(20);
unique_numbers.insert(10); // 重复插入,不会增加

// 检查是否存在
if unique_numbers.contains(&10) {
    println!("找到了数字 10");
}

// 遍历集合
for num in &unique_numbers {
    println!("数字:{}", num);
}

注释:HashSet 保证元素唯一,适合用于去重、判断成员关系等场景,比如检查用户是否已注册。

HashMap:键值对的高效存储

use std::collections::HashMap;

let mut scores = HashMap::new();

// 插入键值对
scores.insert("Alice", 95);
scores.insert("Bob", 87);

// 获取值(返回 Option)
if let Some(score) = scores.get("Alice") {
    println!("Alice 的分数是:{}", score);
}

// 更新值
scores.entry("Bob").and_modify(|s| *s += 5); // Bob 加 5 分

// 遍历键值对
for (name, score) in &scores {
    println!("{} 的分数是:{}", name, score);
}

注释:entry 方法是 HashMap 的“智能插入”机制,可以避免重复查找;and_modify 用于在键存在时修改其值。


字符串类型解析:String 与 &str 的区别

在 Rust 中,字符串有两个主要类型:String&str。它们的关系就像“可变容器”与“只读视图”。

String:可变的字符串

let mut text = String::new();
text.push_str("Hello, ");
text.push_str("Rust!");

println!("{}", text); // 输出:Hello, Rust!

注释:String 是堆上分配的可变字符串,支持动态增长。push_str 用于追加字符串,push 用于追加单个字符。

&str:字符串字面量与引用

// 字符串字面量是 &str 类型
let greeting = "你好,世界!";

// 作为参数传入函数
fn greet(msg: &str) {
    println!("{}", msg);
}

greet(greeting); // OK
greet("欢迎来到 Rust!"); // 也可以直接传字面量

注释:&str 是字符串切片,不拥有数据,只是引用。它轻量、高效,适合传参或临时使用。


字符串操作与常见陷阱

Rust 的字符串操作非常强大,但也有“坑”需要注意。比如字符串索引访问在 Rust 中是不支持的,因为字符串是 UTF-8 编码的,每个字符长度不一。

错误示例:直接索引字符串

let name = "张三";

// ❌ 编译错误!不能用索引访问字符串
// let first_char = name[0]; // 编译失败!

注释:Rust 不允许 name[0] 这种写法,因为 UTF-8 中“张”占 3 个字节,索引 0 并不代表第一个字符。

正确做法:使用迭代器或 chars 方法

let name = "张三";

// 使用 chars() 方法获取字符迭代器
for c in name.chars() {
    println!("字符:{}", c);
}

// 获取第一个字符
if let Some(first) = name.chars().next() {
    println!("第一个字符是:{}", first);
}

注释:chars() 返回一个迭代器,逐个返回 Unicode 字符,是处理字符串的推荐方式。


实际应用:用户输入处理与去重

我们来做一个小项目:读取用户输入的姓名列表,去重并输出。

use std::collections::HashSet;
use std::io;

fn main() {
    let mut input = String::new();
    let mut names = HashSet::new();

    println!("请输入姓名,用空格分隔:");
    
    // 读取用户输入
    io::stdin().read_line(&mut input).expect("读取失败");

    // 按空格分割,并去重
    for name in input.trim().split_whitespace() {
        names.insert(name.to_string());
    }

    // 输出结果
    println!("去重后的姓名列表:");
    for name in &names {
        println!("{}", name);
    }
}

注释:split_whitespace() 按空白字符(空格、换行等)分割;to_string()&str 转为 String,便于插入 HashSettrim() 去除首尾空白。


总结与进阶建议

Rust 集合与字符串的设计,体现了“安全”与“性能”的平衡。Vec<T> 提供了灵活的动态数组支持,HashSetHashMap 让查找变得高效,而 String&str 的区分则帮助开发者避免内存错误。

掌握这些类型,不仅能写出更安全的代码,还能在实际项目中避免常见的陷阱,比如字符串索引越界、重复数据、内存泄漏等。

建议你在练习时尝试以下任务:

  • Vec 实现一个简单的任务列表
  • HashMap 统计一段文本中每个词的出现次数
  • HashSet 实现一个去重工具

当你能熟练运用这些类型时,你就真正迈入了 Rust 的核心世界。继续深入,你会发现,Rust 的强大,不只在语法,更在它对数据的深刻理解。

Rust 集合与字符串,是通往高效、安全编程的第一道门。愿你在学习中,既能理解其原理,也能写出优雅的代码。