rust 容器
Rust 标准库提供了很多常用容器。本章先介绍最常见的四个:
String:拥有所有权的 UTF-8 字符串。Vec<T>:连续内存上的动态数组。HashMap<K, V>:基于哈希表的键值映射。VecDeque<T>:双端队列,适合头尾两端高效插入和删除。
这几个容器都遵守 Rust 的所有权规则。理解它们不仅是学习标准库 API,也是继续理解所有权、借用、迭代器和集合操作的基础。
1. String
1.1 概述
String 是标准库提供的可增长字符串类型,内容使用 UTF-8 编码。
它和字符串字面量 &str 的区别是:
String拥有字符串内容,可以增长、修改、移动。&str是字符串切片,只是借用一段 UTF-8 字符串内容。
1.2 创建字符串
功能:创建一个新的 String。
常用方式:
#![allow(unused)]
fn main() {
let mut s1 = String::new();
let s2 = String::from("hello");
let s3 = "world".to_string();
}
String::new():创建空字符串。String::from(...):从字符串字面量创建。.to_string():把实现了Display的值转换成字符串。
1.3 追加内容
功能:向字符串末尾追加内容。
接口签名:
#![allow(unused)]
fn main() {
pub fn push_str(&mut self, string: &str)
pub fn push(&mut self, ch: char)
}
push_str追加字符串切片。push追加单个字符。
简单调用示例:
#![allow(unused)]
fn main() {
let mut s = String::from("Rust");
s.push(' ');
s.push_str("book");
println!("{}", s);
}
1.4 遍历字符串
Rust 字符串是 UTF-8,不能随便按字节索引字符。
常见遍历方式:
#![allow(unused)]
fn main() {
let s = String::from("你好 Rust");
for ch in s.chars() {
println!("{}", ch);
}
for byte in s.bytes() {
println!("{}", byte);
}
}
chars():按 Unicode 标量值遍历。bytes():按 UTF-8 字节遍历。
1.5 解析字符串
功能:把字符串解析成目标类型。
接口签名:
#![allow(unused)]
fn main() {
pub fn parse<F>(&self) -> Result<F, F::Err>
where
F: FromStr
}
简单调用示例:
#![allow(unused)]
fn main() {
let number = "42".parse::<i32>().unwrap();
println!("{}", number);
}
parse 返回 Result,因为字符串内容不一定能成功解析成目标类型。
2. Vec<T>
2.1 概述
Vec<T> 是动态数组,所有元素类型必须相同。它把元素放在连续内存中,因此按索引访问很快。
适合场景:
- 按顺序存储一组元素。
- 需要尾部追加。
- 需要通过索引读取元素。
2.2 创建 Vec
常用方式:
#![allow(unused)]
fn main() {
let mut v1 = Vec::<i32>::new();
let v2 = vec![1, 2, 3];
}
Vec::new():创建空 Vec。vec![]:用宏直接创建带初始元素的 Vec。
2.3 添加元素
接口签名:
#![allow(unused)]
fn main() {
pub fn push(&mut self, value: T)
}
简单调用示例:
#![allow(unused)]
fn main() {
let mut numbers = Vec::new();
numbers.push(10);
numbers.push(20);
}
push 会把元素追加到尾部。如果容量不够,Vec 会重新分配更大的内存。
2.4 读取元素
常见方式:
#![allow(unused)]
fn main() {
let numbers = vec![10, 20, 30];
let first = numbers[0];
let maybe = numbers.get(10);
}
numbers[index]:越界会panic。numbers.get(index):返回Option<&T>,越界返回None。
2.5 删除元素
常用接口:
#![allow(unused)]
fn main() {
pub fn pop(&mut self) -> Option<T>
pub fn remove(&mut self, index: usize) -> T
}
pop从尾部删除,返回Option<T>。remove删除指定位置元素,并移动后续元素填补空位。
简单调用示例:
#![allow(unused)]
fn main() {
let mut numbers = vec![10, 20, 30];
let last = numbers.pop();
let first = numbers.remove(0);
}
3. HashMap<K, V>
3.1 概述
HashMap<K, V> 是键值映射,适合通过 key 快速查找 value。
使用 HashMap 时,key 需要实现:
Eq:能判断两个 key 是否相等。Hash:能计算哈希值。
3.2 创建 HashMap
#![allow(unused)]
fn main() {
use std::collections::HashMap;
let mut scores = HashMap::<String, i32>::new();
}
3.3 插入元素
接口签名:
#![allow(unused)]
fn main() {
pub fn insert(&mut self, k: K, v: V) -> Option<V>
}
- 如果 key 不存在,插入新键值对,返回
None。 - 如果 key 已存在,覆盖旧值,返回
Some(old_value)。
简单调用示例:
#![allow(unused)]
fn main() {
scores.insert(String::from("Alice"), 95);
scores.insert(String::from("Bob"), 82);
}
3.4 查找元素
接口签名:
#![allow(unused)]
fn main() {
pub fn get<Q>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized
}
简单调用示例:
#![allow(unused)]
fn main() {
if let Some(score) = scores.get("Alice") {
println!("{}", score);
}
}
get 返回 Option<&V>,因为 key 可能不存在。
3.5 entry API
entry 适合“如果没有就插入,如果有就修改”的场景。
#![allow(unused)]
fn main() {
scores.entry(String::from("Carol")).or_insert(90);
*scores.entry(String::from("Alice")).or_insert(0) += 1;
}
3.6 遍历和删除
#![allow(unused)]
fn main() {
for (key, value) in &scores {
println!("{} => {}", key, value);
}
scores.remove("Bob");
}
注意:HashMap 的遍历顺序不稳定,不要依赖输出顺序。
4. VecDeque<T>
4.1 概述
VecDeque<T> 是双端队列,支持从头部和尾部高效插入/删除。
它适合:
- 队列:从尾部
push_back,从头部pop_front。 - 双端任务列表:头尾都可能插入或删除。
VecDeque 内部使用环形缓冲区,不保证所有元素在内存中完全连续。
4.2 创建 VecDeque
#![allow(unused)]
fn main() {
use std::collections::VecDeque;
let mut queue = VecDeque::<String>::new();
}
4.3 头尾插入
接口签名:
#![allow(unused)]
fn main() {
pub fn push_back(&mut self, value: T)
pub fn push_front(&mut self, value: T)
}
简单调用示例:
#![allow(unused)]
fn main() {
queue.push_back(String::from("task-1"));
queue.push_front(String::from("urgent-task"));
}
4.4 头尾删除
接口签名:
#![allow(unused)]
fn main() {
pub fn pop_front(&mut self) -> Option<T>
pub fn pop_back(&mut self) -> Option<T>
}
简单调用示例:
#![allow(unused)]
fn main() {
while let Some(task) = queue.pop_front() {
println!("handle {}", task);
}
}
5. 迭代器
5.1 为什么需要迭代器?
前四节我们分别介绍了 String、Vec、HashMap、VecDeque。它们结构不同,但有一个共同需求:遍历元素。如果每种容器都用自己的方式遍历,代码就会变成这样:
#![allow(unused)]
fn main() {
// 不同容器,不同遍历方式——很割裂
for i in 0..v.len() { let x = v[i]; } // Vec: 手动索引
for (k, v) in &map { /* ... */ } // HashMap: 解构键值对
while let Some(x) = q.pop_front() { /* ... */ } // VecDeque: 消费式遍历
}
Rust 的做法是:所有容器统一通过迭代器提供遍历能力。迭代器是一个标准化的抽象层——无论容器内部是什么数据结构,对外都暴露同一套操作接口(map、filter、collect 等)。
5.2 Iterator trait
迭代器的核心是 Iterator trait:
#![allow(unused)]
fn main() {
pub trait Iterator {
type Item; // 迭代产出的元素类型
fn next(&mut self) -> Option<Self::Item>; // 核心方法:取下一个元素
// ... 其余 70+ 个方法都有默认实现
}
}
只需要实现 next,其余所有方法(map、filter、collect、fold 等)自动获得。
next 的返回值表达了迭代器的状态:
Some(item)→ 还有元素,返回下一个。None→ 迭代结束,停止。
5.3 三种迭代形式
Rust 容器对“遍历“这件事有三种不同的权限模型:
| 方法 | 产出类型 | 所有权 | 使用场景 |
|---|---|---|---|
.iter() | &T | 借用,不消费容器 | 只读遍历,之后还能用容器 |
.iter_mut() | &mut T | 可变借用,不消费容器 | 遍历中修改元素 |
.into_iter() | T | 消费容器,转移所有权 | 遍历后不再需要原容器 |
#![allow(unused)]
fn main() {
let mut v = vec![1, 2, 3];
// 只读遍历——容器原样保留
for x in v.iter() {
println!("{}", x);
}
println!("容器还在: {:?}", v); // OK
// 可变遍历——可以修改元素
for x in v.iter_mut() {
*x *= 10;
}
println!("元素翻倍: {:?}", v); // [10, 20, 30]
// 消费式遍历——所有权转移,原容器不可再用
let sum: i32 = v.into_iter().sum();
println!("{}", sum);
// println!("{:?}", v); // 编译错误:v 已被消费
}
for循环的语法糖:for x in &v等价于for x in v.iter(),for x in &mut v等价于for x in v.iter_mut(),for x in v等价于for x in v.into_iter()。for循环会自动调用对应的迭代器方法,写起来更简洁。
5.4 迭代器适配器(adaptor)
迭代器的真正威力在于链式组合。适配器接收一个迭代器,返回一个新的迭代器——零拷贝的流水线处理:
#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
// 链式处理:每个适配器返回一个新的迭代器
let result: Vec<i32> = numbers
.iter() // 产生 &i32
.filter(|&&x| x % 2 == 0) // 只保留偶数
.map(|&x| x * 10) // 每个元素乘以 10
.collect(); // 收集到 Vec 中
println!("{:?}", result); // [20, 40, 60]
}
惰性求值:迭代器适配器不会立即执行——只有当你调用 collect()、sum()、for 循环等消费器(consumer)时,整个流水线才开始计算。这意味着中间步骤不分配临时 Vec。
常用适配器一览:
| 适配器 | 作用 | 示例 |
|---|---|---|
.map(|x| ...) | 转换每个元素 | .map(|x| x * 2) |
.filter(|x| ...) | 保留满足条件的元素 | .filter(|x| *x > 0) |
.filter_map(|x| ...) | 过滤 + 转换一步完成 | .filter_map(|x| x.parse().ok()) |
.take(n) | 只取前 n 个 | .take(5) |
.skip(n) | 跳过前 n 个 | .skip(1) |
.enumerate() | 附加索引 (index, item) | .enumerate() |
.chain(other) | 拼接两个迭代器 | .chain(b.iter()) |
.zip(other) | 成对组合 | .zip(b.iter()) |
.rev() | 反向遍历(仅双端迭代器) | .rev() |
5.5 消费器(consumer)
消费器触发流水线执行,并产出最终结果:
#![allow(unused)]
fn main() {
let v = vec![1, 2, 3, 4, 5];
// collect: 收集到任意集合类型
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
let doubled_set: std::collections::HashSet<i32> = v.iter().map(|x| x * 2).collect();
// fold: 带初始值的累加
let sum = v.iter().fold(0, |acc, x| acc + x); // 15
// reduce: 无初始值的累加(返回 Option)
let product = v.iter().copied().reduce(|a, b| a * b); // Some(120)
// sum / product: 内置的求和/求积
let total: i32 = v.iter().sum(); // 15
// any / all: 条件判断
let has_even = v.iter().any(|x| x % 2 == 0); // true
let all_positive = v.iter().all(|x| *x > 0); // true
// count: 计数
let n = v.iter().filter(|x| **x > 2).count(); // 3
// find / position: 查找
let first = v.iter().find(|x| **x > 3); // Some(&4)
let idx = v.iter().position(|x| *x == 3); // Some(2)
// max / min: 极值
let max = v.iter().max(); // Some(&5)
let min = v.iter().min(); // Some(&1)
}
5.6 和四种容器的配合
迭代器与本章四种容器的常见用法:
String:
#![allow(unused)]
fn main() {
let s = String::from("Rust");
let upper: String = s.chars().map(|c| c.to_ascii_uppercase()).collect();
// chars() 返回 chars 迭代器,collect() 收集回 String
}
Vec:
#![allow(unused)]
fn main() {
let words = vec!["hello", "world"];
let lengths: Vec<usize> = words.iter().map(|w| w.len()).collect();
}
HashMap:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
let scores = HashMap::from([("Alice", 95), ("Bob", 82)]);
let names: Vec<&str> = scores.keys().copied().collect();
let high_scores: HashMap<_, _> = scores
.into_iter()
.filter(|(_, v)| *v > 80)
.collect();
}
VecDeque:
#![allow(unused)]
fn main() {
use std::collections::VecDeque;
let q = VecDeque::from([1, 2, 3]);
let doubled: VecDeque<i32> = q.iter().map(|x| x * 2).collect();
}
5.7 迭代器的性能
Rust 迭代器是零成本抽象——经过编译器优化后,链式迭代器调用往往被内联展开成与手写 for 循环等效甚至更优的机器码。你可以放心使用迭代器,不用为“抽象开销“担心。
举个例子,下面两段代码生成的机器码几乎一样:
#![allow(unused)]
fn main() {
// 迭代器版本
let sum: i32 = v.iter().filter(|x| **x > 0).map(|x| x * 2).sum();
// 手写循环版本
let mut sum = 0;
for x in &v {
if *x > 0 {
sum += x * 2;
}
}
}
迭代器版本可读性更好,性能没有损失——这正是 Rust “无代价抽象” 哲学的体现。
6. 综合示例:任务看板
这个示例把四种容器放在一起使用:
String保存原始输入。VecDeque<String>保存待处理任务队列。HashMap<String, String>保存任务负责人。Vec<String>保存已完成任务。
#![allow(unused)]
fn main() {
use std::collections::{HashMap, VecDeque};
pub fn demo() {
let raw_tasks = String::from("learn rust,write demo,read book");
let mut waiting = VecDeque::<String>::new();
let mut finished = Vec::<String>::new();
let mut owners = HashMap::<String, String>::new();
for task in raw_tasks.split(',') {
waiting.push_back(task.trim().to_string());
}
owners.insert(String::from("learn rust"), String::from("Alice"));
owners.insert(String::from("write demo"), String::from("Bob"));
owners.insert(String::from("read book"), String::from("Carol"));
while let Some(task) = waiting.pop_front() {
let owner = owners
.get(&task)
.map(String::as_str)
.unwrap_or("unknown");
println!("{} is handling {}", owner, task);
finished.push(task);
}
println!("finished tasks = {:?}", finished);
}
}