前几篇我们用的都是固定长度的数组和元组。实际开发中,数据量在编译期往往未知——用户列表、文本内容、配置映射……这一篇学习 Rust 三大动态集合类型:Vec(变长数组)、String(文本)和 HashMap(键值对)。
1. Vec:动态数组
Vec<T> 是堆上分配的变长数组。所有元素类型相同。
1.1 创建与基本操作
// 空 Vec
let mut v: Vec<i32> = Vec::new();
v.push(10);
v.push(20);
v.push(30);
// vec! 宏快速创建
let v2 = vec![1, 2, 3, 4, 5];
println!("v = {v:?}, v2 = {v2:?}");
println!("v 长度: {}, 第一个元素: {}", v.len(), v[0]);
1.2 遍历
let v = vec![10, 20, 30, 40, 50];
// 不可变遍历
for elem in &v {
print!("{elem} ");
}
// 可变遍历
let mut v = vec![1, 2, 3];
for elem in &mut v {
*elem *= 10; // * 解引用,修改实际值
}
println!("翻十倍: {v:?}"); // [10, 20, 30]
注意 *elem 的解引用——elem 是 &mut i32 类型,要修改它指向的值就必须先解开引用。
1.3 常用方法
let mut v = vec![1, 2, 3, 4, 5];
// get:安全索引,返回 Option<&T>
v.get(2); // Some(&3)
v.get(99); // None——不会 panic
// pop:弹出最后一个
let last = v.pop(); // Some(5)
// insert / remove
v.insert(0, 0); // [0, 1, 2, 3, 4]
v.remove(2); // [0, 1, 3, 4]
索引 [] vs get():v[99] 越界会直接 panic;v.get(99) 返回 None。严肃代码优先用 get。
2. String:不止是字符串
Rust 的 String 是堆上分配的、可增长的 UTF-8 文本。和 &str(字符串切片、存引用)不同,String 拥有自己的数据。
let mut s = String::from("hello");
s.push_str(", world"); // 追加字符串切片
s.push('!'); // 追加单个字符
println!("{s}"); // hello, world!
2.1 拼接
用 + 拼接时,左边的 String 所有权会被拿走:
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1 被移动,s2 是借用
println!("{s3}");
// println!("{s1}"); // ❌ s1 已失效
不想丢所有权,用 format! 宏:
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{s1}-{s2}-{s3}"); // 三个都不丢所有权
println!("{s}");
println!("{s1}"); // ✅ 还能用
3. HashMap:键值对
HashMap<K, V> 存一组键值对,键唯一,基于哈希实现。
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert("Alice", 90);
scores.insert("Bob", 85);
3.1 访问
match scores.get("Alice") {
Some(score) => println!("Alice 的分数: {score}"),
None => println!("不存在"),
}
get 返回 Option<&V>,和 Vec::get 一样安全。
3.2 遍历
for (name, score) in &scores {
println!("{name}: {score}");
}
// 输出顺序不保证
3.3 entry API:更新或插入
这是 HashMap 最常用的模式——根据键是否存在来决定操作:
scores.entry("Alice").or_insert(95); // Alice 已存在,值不变
scores.entry("Charlie").or_insert(80); // Charlie 不存在,插入 80
3.4 基于旧值更新
entry 返回 Entry 枚举,or_insert 返回值的可变引用,可以基于旧值修改:
let text = "hello world wonderful world";
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
let count = word_count.entry(word).or_insert(0);
*count += 1; // 解引用,累加
}
// {"hello": 1, "world": 2, "wonderful": 1}
这个模式在统计、计数场景下非常常用——一行 entry().or_insert() 替代了其他语言里「查键 → 判空 → 更新/插入」的三步曲。
总结
| 类型 | 对应 | 关键操作 |
|---|---|---|
Vec<T> | 动态数组 | push / pop / get / vec![] |
String | 堆上可增长 UTF-8 字符串 | push_str / push / format! |
HashMap<K,V> | 键值对 | insert / get / entry().or_insert() |
这三种类型覆盖了日常开发 90% 的数据组织需求。下一篇进入错误处理:Result 与 Option 的实战用法,以及 ? 运算符如何让错误传递简洁优雅。