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。
哈希集合与映射:高效查找的利器
当你需要快速判断某个值是否存在,或者根据键查找值时,HashSet 和 HashMap 就派上用场了。它们底层使用哈希表实现,平均查找时间复杂度为 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,便于插入HashSet;trim()去除首尾空白。
总结与进阶建议
Rust 集合与字符串的设计,体现了“安全”与“性能”的平衡。Vec<T> 提供了灵活的动态数组支持,HashSet 和 HashMap 让查找变得高效,而 String 与 &str 的区分则帮助开发者避免内存错误。
掌握这些类型,不仅能写出更安全的代码,还能在实际项目中避免常见的陷阱,比如字符串索引越界、重复数据、内存泄漏等。
建议你在练习时尝试以下任务:
- 用
Vec实现一个简单的任务列表 - 用
HashMap统计一段文本中每个词的出现次数 - 用
HashSet实现一个去重工具
当你能熟练运用这些类型时,你就真正迈入了 Rust 的核心世界。继续深入,你会发现,Rust 的强大,不只在语法,更在它对数据的深刻理解。
Rust 集合与字符串,是通往高效、安全编程的第一道门。愿你在学习中,既能理解其原理,也能写出优雅的代码。