跳至内容

集合类型

这篇讲 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 链式调用,数据处理代码简洁高效。下一篇讲 错误处理

练习

  1. 给定一个 i32 数组 vec![1, 2, 3, 4, 5, 6, 7, 8],用迭代器过滤出偶数、乘以 2、收集到新的 Vec。
  2. 用 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}
}
最后更新于