集合类型
这篇讲 Rust 最常用的三种集合类型——Vec<T>、HashMap<K, V> 和 String——以及迭代器的基本用法。与数组和元组不同,集合的数据存储在堆上,大小可以动态变化。
Vec<T>(动态数组)
创建与添加元素
fn main() {
// 创建空 Vec
let mut v: Vec<i32> = Vec::new();
// vec! 宏创建带初始值的 Vec
let mut v2 = vec![1, 2, 3];
// 添加元素
v.push(5);
v.push(6);
v2.push(4);
println!("v: {:?}, v2: {:?}", v, v2); // v: [5, 6], v2: [1, 2, 3, 4]
}访问元素
fn main() {
let v = vec![10, 20, 30, 40];
// 索引语法——越界时 panic
let third = v[2];
println!("第三个元素: {third}");
// get 方法——返回 Option,越界时返回 None
match v.get(2) {
Some(val) => println!("第三个元素: {val}"),
None => println!("没有第三个元素"),
}
// 超出范围
// let _oops = v[100]; // panic!
let _safe = v.get(100); // None
}v[idx] 越界会 panic(适合确定索引有效时),v.get(idx) 返回 Option(适合索引可能无效时)。Rust 的哲学是:让可能失败的操作在类型层面可见。遍历
fn main() {
let mut v = vec![100, 32, 57];
// 不可变遍历
for i in &v {
println!("{i}");
}
// 可变遍历——修改每个元素
for i in &mut v {
*i += 50; // * 解引用操作符
}
println!("{:?}", v); // [150, 82, 107]
}常用操作速查
let mut v = vec![1, 2, 3, 4, 5];
v.len(); // 长度: 5
v.is_empty(); // 是否为空: false
v.pop(); // 弹出最后一个: Some(5),v 现在是 [1, 2, 3, 4]
v.insert(0, 99); // 在索引 0 处插入: [99, 1, 2, 3, 4]
v.remove(0); // 移除索引 0: 99,返回被移除的值
v.sort(); // 排序
v.reverse(); // 反转
v.contains(&3); // 是否包含 3: true
String
Rust 中的字符串操作比其他语言稍微复杂——这是字符串编码本身固有的复杂性,Rust 选择不在类型系统中掩盖它。
String vs &str
&str:字符串切片——对字符串数据的引用,不可变,通常指向字符串字面量或其他 String 的一部分String:拥有所有权的字符串——分配在堆上,可变,可以增长
fn main() {
let literal = "hello"; // &str,存储在二进制只读段
let mut owned = String::from("hello"); // String,分配在堆上
owned.push_str(", world!");
owned.push('!');
println!("{owned}"); // hello, world!!
}常用操作
fn main() {
let mut s = String::new();
// 拼接
s.push_str("hello");
s += " world"; // 等同于 push_str
s = s + "!"; // + 会获取左侧所有权
// 用 format! 宏拼接(推荐,不获取所有权)
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let combined = format!("{s1}-{s2}-{s3}"); // "tic-tac-toe"
// 索引(不能直接用 s[0],因为 UTF-8 编码下索引不一定对应字符边界)
// 遍历字符
for c in "你好".chars() {
println!("{c}"); // 你 \n 好
}
// 遍历字节
for b in "你好".bytes() {
println!("{b}"); // 各字节的数值
}
}Rust 不允许用索引直接访问字符串(如
s[0]),因为 String 是 UTF-8 编码,一个字符可能占 1-4 个字节。要用 chars() 逐字符遍历,或用切片(需确保在字符边界上)。HashMap<K, V>
use std::collections::HashMap;
fn main() {
// 创建
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
// 用 collect 从 Vec 构建
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores2: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
// 访问
let team_name = String::from("Blue");
match scores.get(&team_name) {
Some(score) => println!("Blue 队得分: {score}"),
None => println!("Blue 队不存在"),
}
// 遍历
for (key, value) in &scores {
println!("{key}: {value}");
}
// 只在键不存在时插入
scores.entry(String::from("Blue")).or_insert(99); // Blue 已存在,不改变
scores.entry(String::from("Red")).or_insert(60); // Red 不存在,插入 60
// 更新已有值
let blue_score = scores.entry(String::from("Blue")).or_insert(0);
*blue_score += 10; // Blue 现在是 20
println!("{:?}", scores); // {"Blue": 20, "Yellow": 50, "Red": 60}
}entry().or_insert() 是 Rust HashMap 的招牌操作——检查键是否存在,不存在则插入默认值。这在 Go 中通常要写 3-4 行 if-else 才能完成。迭代器
迭代器是 Rust 处理集合的统一抽象,惰性求值,零成本抽象:
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 创建迭代器(惰性——此时还没做任何事)
let iter = v.iter();
// 消费迭代器
let sum: i32 = iter.sum();
println!("sum = {sum}"); // 15
// 迭代器适配器——链式变换(惰性,仍然没做事)
let doubled: Vec<i32> = v.iter()
.map(|x| x * 2) // 每个元素乘以 2
.filter(|x| x % 3 != 0) // 过滤掉 3 的倍数
.collect(); // collect() 触发执行
println!("{:?}", doubled); // [2, 4, 8, 10]
}常用迭代器方法:
| 方法 | 说明 |
|---|---|
.map(|x| ...) | 转换每个元素 |
.filter(|x| ...) | 过滤元素 |
.enumerate() | 添加索引 (0, elem)、(1, elem)… |
.zip(iter) | 与另一个迭代器配对 |
.collect() | 收集到集合 |
.fold(init, |acc, x| ...) | 累积,类似 reduce |
.any(|x| ...) | 是否存在元素满足条件 |
.find(|x| ...) | 找到第一个满足条件的元素 |
一句话小结
Vec<T> 动态数组、HashMap<K, V> 键值对、String 可变字符串是 Rust 三大集合类型。配合迭代器的 map/filter/collect 链式调用,数据处理代码简洁高效。下一篇讲 错误处理。
练习
- 给定一个 i32 数组
vec![1, 2, 3, 4, 5, 6, 7, 8],用迭代器过滤出偶数、乘以 2、收集到新的 Vec。 - 用 HashMap 实现一个词频统计器:输入一个句子 &str,统计每个单词出现的次数。
参考答案
use std::collections::HashMap;
fn main() {
// 练习 1
let nums = vec![1, 2, 3, 4, 5, 6, 7, 8];
let result: Vec<i32> = nums.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * 2)
.collect();
println!("{:?}", result); // [4, 8, 12, 16]
// 练习 2
let text = "hello world hello rust world rust rust";
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
let count = word_count.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", word_count);
// {"world": 2, "rust": 3, "hello": 2}
}最后更新于