Skip to content

迭代器

Iterator 抽象、适配器与消费器全表、自定义迭代器与「边遍历边改」。

迭代器:IteratorIntoIterator

Iterator trait 的形状

text
// std::iter::Iterator 的核心(简化)
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // …… 70+ 个默认方法(map/filter/fold/...)
}

三件事必须一次记住:

  1. next(&mut self):迭代是可变状态推进,所以迭代器变量要能改(for 帮你处理了);
  2. Option<Self::Item>None 表示结束,因此「空迭代器」和「已耗尽」是同一个表示;
  3. Item 是关联类型:每个迭代器只产出一种元素类型,编译期完全确定。
rust
fn main() {
    let v = vec![1, 2, 3];
    let mut it = v.iter();              // Iter<'_, i32>,Item = &i32

    assert_eq!(it.next(), Some(&1));
    assert_eq!(it.next(), Some(&2));
    assert_eq!(it.next(), Some(&3));
    assert_eq!(it.next(), None);
    assert_eq!(it.next(), None);        // 耗尽后再调还是 None(约定:耗尽后必须一直返回 None)
}

for 循环的脱糖(desugaring)是理解一切的关键:

text
// for x in thing { body }
// 展开为:
{
    let mut iter = IntoIterator::into_iter(thing);
    loop {
        match Iterator::next(&mut iter) {
            Some(x) => body,
            None => break,
        }
    }
}

IntoIterator 与三种迭代方式

for 循环要的不是 Iterator,而是 IntoIterator(「能变成迭代器」):

text
trait IntoIterator {
    type Item;
    type IntoIter: Iterator<Item = Self::Item>;
    fn into_iter(self) -> Self::IntoIter;
}

Vec<T> 而言存在三个实现,构成三种迭代方式;for 的写法与方法调用是一一对应的

for 写法等价的方法链Item是否移动容器元素可否修改
for x in &vfor x in v.iter()&T否(共享借用)❌ 只能读
for x in &mut vfor x in v.iter_mut()&mut T否(独占借用)✅ 就地改
for x in vfor x in v.into_iter()T是(v 被消费)✅ 拿到所有权
rust
fn main() {
    let mut v = vec![String::from("a"), String::from("b")];

    for s in &v {                       // Item = &String
        println!("读: {s}");
    }

    for s in &mut v {                   // Item = &mut String
        s.push('!');                    // 就地修改,不改变长度
    }
    println!("{v:?}");                  // 输出:["a!", "b!"]

    for s in v {                        // Item = String,v 被移动
        println!("拥有: {s}");
    }
    // println!("{v:?}");               // E0382:v 已经被 into_iter 消费
}

其他常见集体的三种方式:

类型&xItem&mut xItemxItem
Vec<T> / [T; N]&T&mut TT
HashMap<K, V>(&K, &V)(&K, &mut V)(K, V)
HashSet<T> / BTreeSet<T>&T❌ 无 &mut 实现T
Stringchar
&strchar
BTreeMap<K, V>(&K, &V)(&K, &mut V)(K, V)

⚠️ 陷阱String&str 没有 &String / &&strIntoIteratorfor c in &s 编译失败。想遍历字符写 for c in s.chars()(借用)或 for c in s.chars()

🧠 原理v.iter() 之所以存在,是因为 Vec<T> 通过 Deref 拿到 [T]iter 方法 —— Vec 自己没定义 iter(&v).into_iter()v.iter() 结果完全一样(标准库的 impl IntoIterator for &Vec<T> 内部就是 self.iter()), 但 &v 形式更简短,且能用于任何 IntoIterator 的场合(如函数参数 impl IntoIterator)。

惰性求值:适配器不执行,直到被消费

迭代器适配器(iterator adapter)返回的是一个新的迭代器,本身什么都不做。

rust
fn main() {
    let v = vec![1, 2, 3];

    let pipeline = v.iter().map(|x| {
        println!("map 被调用: {x}");
        x * 2
    });

    println!("—— 到这里为止,一行 map 都没执行 ——");
    let total: i32 = pipeline.sum();          // 消费时才逐元素驱动
    println!("total = {total}");
}

输出:

—— 到这里为止,一行 map 都没执行 ——
map 被调用: 1
map 被调用: 2
map 被调用: 3
total = 12

这个性质叫惰性求值(lazy evaluation),带来两个直接好处:

  1. 没有中间容器v.iter().map(f).filter(g).sum() 不会先建 Vec 再建 Vec 再求和, 而是一个循环走完(对比 JS 的 arr.map(f).filter(g) 会创建两个新数组)。
  2. 可以表达无限序列(1..).filter(|x| x % 3 == 0).take(5) 是安全的, 因为 take(5) 会在取够后停止驱动整个链。
rust
fn main() {
    let first_five: Vec<u32> = (1u32..).filter(|x| x % 3 == 0).take(5).collect();
    println!("{first_five:?}");        // 输出:[3, 6, 9, 12, 15]

    // 反面:无限迭代器 + 立即消费 = 永不结束
    // let boom: Vec<u32> = (1u32..).collect();     // 会一直分配内存直到 OOM
}

⚠️ 陷阱:迭代器适配器不执行,所以「忘了消费」是静默 bug: v.iter().map(|x| do_something(x)); 编译会有 unused_must_use 警告 (iterator adapters 标了 #[must_use]),但警告不等于错误 —— 请把 cargo clippy -D warnings 加进 CI。 需要「立即执行副作用」的场合就用 for_each(消费器,见「迭代器消费器全表」)。

迭代器 vs 索引循环:为什么迭代器常常更快

rust
fn sum_index(v: &[i32]) -> i64 {
    let mut acc = 0i64;
    for i in 0..v.len() {
        acc += v[i] as i64;            // 每次索引都要做一次「i < len」边界检查
    }
    acc
}

fn sum_iter(v: &[i32]) -> i64 {
    v.iter().map(|&x| x as i64).sum()  // 迭代器把「游标 + 末尾指针」交给优化器
}

fn main() {
    let v: Vec<i32> = (0..100).collect();
    assert_eq!(sum_index(&v), sum_iter(&v));
    println!("{}", sum_iter(&v));      // 输出:4950
}

两者通常生成同样的机器码,但迭代器版本更容易保持在最优点:

  1. 边界检查消除(bounds check elision)v[i] 的检查是运行期分支, LLVM 需要证明 i < len 才能删掉它。slice::Iter 内部维护的「当前指针 < 末尾指针」 是循环条件本身,检查因此不存在(不是被删掉,而是根本不需要)。 索引循环里 LLVM 也常能推导出来,但一旦循环体里有函数调用、提前 break、 或索引表达式复杂化,推导就可能失败。
  2. 不需要重新加载长度for i in 0..v.len()v.len() 只在循环外算一次, 但如果有 v.push/v.truncate 这类可能改变长度的调用混在里面, 编译器就必须保守地重新加载 len 并重做检查;迭代器借用整个切片,结构上不可能改长度。
  3. zip 对齐两个切片for i in 0..a.len() { a[i] + b[i] } 每次要做两个检查, 而 a.iter().zip(&b) 只比较两个指针,检查为 0 次,且天然处理长度不等。
  4. 单态化(monomorphization):泛型的 map/filter 会为每个具体闭包类型 生成专用代码,闭包被内联,整条链融成一个循环,没有虚函数调用、没有装箱。 这是「零成本抽象(zero-cost abstraction)」在迭代器上最直观的体现。
   v.iter().map(f).filter(g).sum::<i32>()

   你写的:      iter ──► map(f) ──► filter(g) ──► sum()
   编译后:      ┌───────────────────────────────────┐
                 │  一个循环: 取元素 → 内联 f → 内联 g → 累加  │
                 └───────────────────────────────────┘
                 (没有中间的 Iter/Map/Filter 对象,没有分配,
                   没有动态分派 —— 全部被内联并优化掉)

🧠 原理:「零成本」指的是运行期零成本,代价在编译期: 每种 Iterator + 闭包 组合都会生成一份代码,链越长、用到的类型越多, 编译时间与二进制体积越大。若代码体积敏感,可以用 Box<dyn Iterator<Item = T>> 换回动态分派(代价:每次 next 一次间接调用、无法内联)。

🚀 进阶collect() 能一次分配精确容量,是因为标准库为切片/range 等迭代器 保留了精确的 size_hint(内部用 TrustedLen 之类的机制做特化)。 自定义迭代器如实实现 size_hint,你的 collect 也会自动受益(见「自定义迭代器」)。



迭代器适配器全表

适配器(adapter)= 接收迭代器、返回迭代器的方法。它们都是惰性的。

适配器作用短示例
map一对一变换,元素个数不变(1..=3).map(|x| x * 2) → 2, 4, 6
filter保留谓词为真的元素(1..=5).filter(|x| x % 2 == 1) → 1, 3, 5
filter_map「变换 + 过滤」合一:闭包返回 OptionNone 被丢弃["1","x"].iter().filter_map(|s| s.parse::<i32>().ok()) → 1
map_while类似 map,但遇到 None永久停止(1.57 起)[1,2,0,3].iter().map_while(|x| 10i32.checked_div(*x)) → 10, 5
flat_map每个元素展开成一个迭代器,再首尾相接vec![vec![1,2], vec![3]].into_iter().flat_map(|v| v) → 1, 2, 3
flatten摊平「迭代器的迭代器」,等价于 flat_map(|x| x);对 Option/Result 同样有效[Some(1), None, Some(3)].into_iter().flatten() → 1, 3
take最多取前 n 个,不足则提前结束(1..).take(3) → 1, 2, 3
take_while取到谓词首次为假为止(之后即使为真也不再取)[1,2,9,1].into_iter().take_while(|&x| x < 5) → 1, 2
skip跳过前 n 个[1,2,3,4].into_iter().skip(2) → 3, 4
skip_while跳过开头满足谓词的部分(中间的不跳)[0,0,1,0].into_iter().skip_while(|&x| x == 0) → 1, 0
chain两个迭代器首尾相接[1,2].into_iter().chain([3,4]) → 1, 2, 3, 4
zip配对推进,任一耗尽即停(不会 panic[1,2,3].into_iter().zip("ab".chars()) → (1,'a'), (2,'b')
enumerate附带从 0 开始的下标"ab".chars().enumerate() → (0,'a'), (1,'b')
peekable允许 peek() 预看下一个而不消费let mut it = "ab".chars().peekable(); it.peek()Some(&'a')
rev反向迭代(要求 DoubleEndedIterator[1,2,3].into_iter().rev() → 3, 2, 1
cloned&TT,要求 T: Clonevec![String::from("a")].iter().cloned()String
copied&TT,要求 T: Copy(比 cloned 更省)[1,2,3].iter().copied() → 1, 2, 3
step_by每 n 个取 1 个(n == 0 panic)(0..10).step_by(3) → 0, 3, 6, 9
inspect旁路观察每个元素,不改变元素(1..=3).inspect(|x| println!("看: {x}"))
scan带可变状态的有状态 map;返回 None 可提前结束(1..=4).scan(0, |acc, x| { *acc += x; Some(*acc) }) → 1, 3, 6, 10
cycle无限重复整个迭代器(要求 Self: Clone[1,2].into_iter().cycle().take(5) → 1, 2, 1, 2, 1
by_ref借用迭代器而非消费它,便于「先用一部分,再继续用」let head: Vec<_> = it.by_ref().take(2).collect();
slice::windows(n)切片方法:长度固定为 n 的滑动窗口[1,2,3].windows(2) → [1,2], [2,3]
slice::chunks(n)切片方法:每 n 个一组,最后一组可能更短[1,2,3,4,5].chunks(2) → [1,2], [3,4], [5]

⚠️ 陷阱windows/chunks 不是迭代器适配器,而是 [T] 的方法。 它们返回的 Windows/Chunks 确实是迭代器,但只能从切片上调用 (所以 v.windows(2) 走的是 Deref[T]),不能写成 v.iter().windows(2)

几个容易混的适配器,展开看差别:

rust
fn main() {
    let data = [1, 2, 9, 1, 2];

    // filter:整个序列都检查,9 后面的 1、2 仍会被保留
    let f: Vec<i32> = data.iter().copied().filter(|&x| x < 5).collect();
    println!("{f:?}");         // 输出:[1, 2, 1, 2]

    // take_while:9 一出现就永久停止
    let t: Vec<i32> = data.iter().copied().take_while(|&x| x < 5).collect();
    println!("{t:?}");         // 输出:[1, 2]

    // skip_while:只跳过开头连续的 0,中间的不跳
    let s: Vec<i32> = [0, 0, 1, 0].into_iter().skip_while(|&x| x == 0).collect();
    println!("{s:?}");         // 输出:[1, 0]

    // flat_map vs flatten:flatten 是 flat_map 的恒等特化
    let fm: Vec<i32> = vec![vec![1, 2], vec![3]].into_iter().flat_map(|v| v).collect();
    let fl: Vec<i32> = vec![vec![1, 2], vec![3]].into_iter().flatten().collect();
    println!("{fm:?} == {fl:?}");        // 输出:[1, 2, 3] == [1, 2, 3]

    // flatten 对 Option 的妙用:一次丢掉所有 None 并取出值
    let oks: Vec<i32> = ["1", "x", "3"].iter().map(|s| s.parse::<i32>().ok()).flatten().collect();
    println!("{oks:?}");       // 输出:[1, 3]
}

peekable / scan / inspect 各来一个完整例子:

rust
use std::iter::Peekable;
use std::str::Chars;

// peekable:需要「看一个再决定要不要消费」时(词法分析的经典形状)
fn count_doubled(text: &str) -> usize {
    let mut it: Peekable<Chars<'_>> = text.chars().peekable();
    let mut count = 0;
    while let Some(c) = it.next() {
        if it.peek() == Some(&c) {
            count += 1;
            it.next();                    // 把重复的那个吃掉,避免 "aaa" 数成 2 次
        }
    }
    count
}

fn main() {
    println!("{}", count_doubled("aabbc"));       // 输出:2
    println!("{}", count_doubled("aaa"));         // 输出:1

    // scan:需要「有状态 + 可提前终止」时
    let running: Vec<i32> = (1..=5).scan(0, |acc, x| {
        *acc += x;
        if *acc > 10 { None } else { Some(*acc) }   // 返回 None 之后迭代器永久结束
    }).collect();
    println!("{running:?}");                     // 输出:[1, 3, 6, 10]

    // inspect:调试管道时打点,不影响数据流
    let total: i32 = (1..=4)
        .inspect(|x| println!("进入管道: {x}"))
        .filter(|x| x % 2 == 0)
        .inspect(|x| println!("通过 filter: {x}"))
        .sum();
    println!("total = {total}");                 // 输出:6(进入 1~4,通过 2、4)
}

💡 对照:Python 3 的 map/filter 也是惰性的,但没有方法链(要嵌成 filter(g, map(f, xs)) 或生成器表达式),且每个元素都是 Python 对象、每次调用都有 解释器开销。JS 的 Array.prototype.map/filter立即求值创建新数组。 Java 的 Stream 惰性、也有方法链,最接近 Rust;但 Java 的流操作是 接口上的虚调用,Rust 的单态化能做到完全内联 —— 这就是「Rust 迭代器和手写循环一样快」 而 Java Stream 常常比手写循环慢的来源。



迭代器消费器全表

消费器(consumer)= 驱动迭代器并产出最终结果的方法。调用消费器才会真正开始循环。

消费器作用短示例 / 返回类型
collect收集成任意 FromIterator 类型(1..=3).collect::<Vec<_>>()vec![1,2,3]
collect::<Result<Vec<_>, _>>()短路收集:遇到第一个 Err 立即返回它["1","x"].iter().map(|s| s.parse::<i32>()).collect::<Result<Vec<i32>,_>>()Err(..)
collect::<String>()char / &str 拼成 String"abc".chars().collect::<String>()"abc"
sum / product求和 / 求积(要求 T: Sum / T: Product(1..=4).sum::<i32>()10(1..=4).product::<i32>()24
count消耗整个迭代器,返回元素个数"a b c".split(' ').count()3
min / max最小 / 最大(要求 Ord),返回 Option<Item>[3,1,2].iter().max()Some(&3)
min_by_key / max_by_key按 key 比较,返回 Option<Item>["aa","b"].iter().min_by_key(|s| s.len())Some(&"b")
min_by / max_by自定义比较(返回 Orderingv.iter().max_by(|a, b| a.len().cmp(&b.len()))
fold显式携带累加器,最通用(1..=3).fold(0, |a, x| a + x)6
reduce以第一个元素为初值的 fold,返回 Option(1..=3).reduce(|a, x| a + x)Some(6);空迭代器给 None
try_fold可提前返回 Err(或 None)的 fold见下方示例
any / all短路布尔判断(1..5).any(|x| x == 3)true(1..5).all(|x| x > 0)true
find第一个满足条件的元素[1,2,3].iter().find(|&&x| x > 1)Some(&2)
position第一个满足条件的下标[1,2,3].iter().position(|&x| x == 2)Some(1)
rposition / rfind从右往左找(要求 DoubleEndedIterator从末尾方向找
for_each终结式遍历,等价于 for 循环v.iter().for_each(|x| print!("{x}"))
last最后一个元素(会遍历全部(1..=3).last()Some(3)
nth跳过并取第 n 个(消费前 n+1 个)(10..).nth(2)Some(12)
partition一次遍历分进两个集合(1..=5).partition::<Vec<_>, _>(|x| x % 2 == 0)([2,4], [1,3,5])
unzip(A, B) 流拆成两个集合[(1,'a'),(2,'b')].into_iter().unzip::<i32,char,Vec<_>,String>()
is_sorted / is_sorted_by是否已排序(1.82 起)[1,2,3].iter().is_sorted()true

⚠️ 陷阱min_by_key / max_by_key 在多个元素 key 相同时的取舍不对称min_by_key 返回第一个最小值,max_by_key 返回最后一个最大值。 要绝对确定就自己写 min_by / max_by,或先 enumerate 带上序号。

⚠️ 陷阱sum::<i32>() 在 debug 构建下溢出会 panicattempt to add with overflow), 在 release 构建下静默回绕(wrapping)。想两种构建行为一致,用 fold(0i64, |a, x| a + x as i64) 提升到 i64,或显式用 checked_sum 风格(try_fold + checked_add)。

collect 的三种重要用法:

rust
fn main() {
    // 1) 普通收集:需要能推断出目标类型,否则 E0283/E0282
    let squares: Vec<i32> = (1..=4).map(|x| x * x).collect();
    println!("{squares:?}");                 // 输出:[1, 4, 9, 16]

    // 2) Result 收集(短路):任一失败就整体失败
    let good = ["1", "2", "3"];
    let mixed = ["1", "oops", "3"];
    let a: Result<Vec<i32>, _> = good.iter().map(|s| s.parse::<i32>()).collect();
    let b: Result<Vec<i32>, _> = mixed.iter().map(|s| s.parse::<i32>()).collect();
    println!("{a:?}");                       // 输出:Ok([1, 2, 3])
    println!("{:?}", b.map_err(|e| e.to_string()));
    // 输出:Err("invalid digit found in string")

    // 3) 拼成 String / HashMap / HashSet,都是同一个 collect
    let joined: String = ["a", "b", "c"].iter().copied().collect();
    let set: std::collections::HashSet<char> = "hello".chars().collect();
    println!("{joined} / {}", set.len());    // 输出:abc / 4
}

fold / reduce / try_fold 的层次关系:

rust
fn main() {
    // fold:任意初值 + 任意累加器类型(可以与 Item 不同)
    let csv: String = ["a", "b", "c"]
        .iter()
        .fold(String::new(), |mut acc, s| {
            if !acc.is_empty() { acc.push(','); }
            acc.push_str(s);
            acc
        });
    println!("{csv}");                       // 输出:a,b,c

    // reduce:要求 Item 就是累加器类型,空迭代器返回 None
    let max_even: Option<i32> = (1..=10).filter(|x| x % 2 == 0).reduce(|a, b| a.max(b));
    println!("{max_even:?}");                // 输出:Some(10)
    let nothing: Option<i32> = std::iter::empty::<i32>().reduce(|a, b| a + b);
    println!("{nothing:?}");                 // 输出:None

    // try_fold:累加函数返回 Result/Option,出现「失败」立即短路
    let parsed: Result<Vec<i32>, std::num::ParseIntError> = ["1", "2", "3"]
        .iter()
        .try_fold(Vec::new(), |mut acc, s| {
            acc.push(s.parse::<i32>()?);     // ? 在闭包里短路整个 try_fold
            Ok(acc)
        });
    println!("{parsed:?}");                  // 输出:Ok([1, 2, 3])
}

partition / unzip 一次遍历分流:

rust
fn main() {
    let (evens, odds): (Vec<i32>, Vec<i32>) = (1..=6).partition(|x| x % 2 == 0);
    println!("{evens:?} / {odds:?}");        // 输出:[2, 4, 6] / [1, 3, 5]

    let pairs = [(1, 'a'), (2, 'b'), (3, 'c')];
    let (nums, letters): (Vec<i32>, String) = pairs.into_iter().unzip();
    println!("{nums:?} / {letters}");        // 输出:[1, 2, 3] / abc
}


自定义迭代器

实现 Iterator for Counter(含 size_hint

rust
/// 依次产出 1, 2, 3, 4, 5,然后结束。
struct Counter {
    count: u32,
    max: u32,
}

impl Counter {
    fn new(max: u32) -> Self {
        Counter { count: 0, max }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        if self.count < self.max {
            self.count += 1;
            Some(self.count)
        } else {
            None                          // 一旦返回 None,之后必须一直是 None
        }
    }

    /// 上下界都精确:collect 能一次分配到位,take/skip 也能走快路径
    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = (self.max - self.count) as usize;
        (remaining, Some(remaining))
    }
}

fn main() {
    // 自定义迭代器自动获得全部适配器与消费器(默认方法)
    let doubled: Vec<u32> = Counter::new(5).map(|x| x * 2).collect();
    println!("{doubled:?}");                       // 输出:[2, 4, 6, 8, 10]

    // Iterator 有 blanket impl:impl<I: Iterator> IntoIterator for I,
    // 所以自定义迭代器直接就能用 for 循环
    let mut sum = 0;
    for x in Counter::new(4) {
        sum += x;
    }
    println!("{sum}");                             // 输出:10

    let mut it = Counter::new(5);
    println!("{:?}", it.size_hint());               // 输出:(5, Some(5))
    it.next();
    println!("{:?}", it.size_hint());               // 输出:(4, Some(4))

    println!("{}", Counter::new(5).count());        // 输出:5
    println!("{:?}", Counter::new(5).zip("abc".chars()).collect::<Vec<_>>());
    // 输出:[(1, 'a'), (2, 'b'), (3, 'c')]
}

关于 size_hint 的契约(必须准确,否则是逻辑 bug):

  • 返回值是 (下界, 上界),表示 next() 还能产出多少个元素;
  • 下界必须 ≤ 真实数量上界必须 ≥ 真实数量(估计过小/过大都是错误);
  • 上界是 None 表示「不知道上限」(比如无限迭代器);
  • 默认实现是 (0, None) —— 保守但会让 collect 失去预分配优化;
  • 耗尽后(next 返回 None必须返回 (0, Some(0)),否则 collect 可能分配出错误容量。

⚠️ 陷阱:不要为了性能在 size_hint 里「撒小谎」。上界报小了会让标准库 依赖它的优化(如 Vec::with_capacity 后的 unsafe 写入路径)产生未定义行为; 这是安全相关的契约,不是提示。

实现 IntoIterator for &MyCollection

Iterator 是「自己能被推进」,IntoIterator 是「能被 for 消费」。 自定义容器要支持三种遍历,就要写三个 impl

rust
struct Playlist {
    tracks: Vec<String>,
}

impl Playlist {
    fn new(tracks: &[&str]) -> Self {
        Playlist { tracks: tracks.iter().map(|s| s.to_string()).collect() }
    }
}

// &Playlist:for t in &playlist(Item = &String)
impl<'a> IntoIterator for &'a Playlist {
    type Item = &'a String;
    type IntoIter = std::slice::Iter<'a, String>;

    fn into_iter(self) -> Self::IntoIter {
        self.tracks.iter()
    }
}

// &mut Playlist:for t in &mut playlist(Item = &mut String)
impl<'a> IntoIterator for &'a mut Playlist {
    type Item = &'a mut String;
    type IntoIter = std::slice::IterMut<'a, String>;

    fn into_iter(self) -> Self::IntoIter {
        self.tracks.iter_mut()
    }
}

// Playlist:for t in playlist(Item = String,容器被消费)
impl IntoIterator for Playlist {
    type Item = String;
    type IntoIter = std::vec::IntoIter<String>;

    fn into_iter(self) -> Self::IntoIter {
        self.tracks.into_iter()
    }
}

fn main() {
    let mut pl = Playlist::new(&["intro", "verse", "outro"]);

    for t in &pl {
        println!("读: {t}");
    }

    for t in &mut pl {
        t.push_str(" (live)");
    }
    for t in &pl {
        println!("改后: {t}");          // 输出:intro (live) / verse (live) / outro (live)
    }

    let owned: Vec<String> = pl.into_iter().collect();   // pl 被消费
    println!("{}", owned.len());        // 输出:3
}

🧠 原理for x in &collection 之所以能用,是因为存在 impl IntoIterator for &Playlist。标准库对 Vec/HashMap 等都预置了这三个实现, 所以你的自定义容器也应该提供三个,用户才感觉「和内置集合一样」。

impl Iterator<Item = ...> 作为返回类型

不想为每个管道写一个命名结构体,就直接返回 impl Iterator

rust
// edition 2024:不写 + '_ 也合法(RPIT 默认捕获所有在作用域内的生命周期)
fn evens(values: &[i32]) -> impl Iterator<Item = i32> {
    values.iter().copied().filter(|x| x % 2 == 0)
}

// 返回借用型迭代器:生命周期在 2024 里自动被捕获
fn words(text: &str) -> impl Iterator<Item = &str> {
    text.split_whitespace()
}

// 需要「根据条件返回两种不同迭代器」时,要么用 Box<dyn Iterator>,要么用 Either 风格
fn numbers(only_positive: bool, data: &[i32]) -> Box<dyn Iterator<Item = i32> + '_> {
    if only_positive {
        Box::new(data.iter().copied().filter(|x| *x > 0))
    } else {
        Box::new(data.iter().copied())
    }
}

fn main() {
    println!("{:?}", evens(&[1, 2, 3, 4, 5]).collect::<Vec<_>>());   // 输出:[2, 4]
    println!("{:?}", words("a b c").collect::<Vec<_>>());           // 输出:["a", "b", "c"]
    println!("{:?}", numbers(true, &[-1, 2]).collect::<Vec<_>>());  // 输出:[2]
    println!("{:?}", numbers(false, &[-1, 2]).collect::<Vec<_>>()); // 输出:[-1, 2]
}
返回迭代器的三种方式优点缺点
impl Iterator<Item = T>零成本、可内联只能返回一种具体类型;不能用作结构体字段(需泛型参数)
impl Iterator<Item = T> + '_明确借用来源(2021 及更早必须写)语法噪音
Box<dyn Iterator<Item = T> + '_>可以返回不同类型、可作为字段每次 next 一次虚调用,无法内联

🚀 进阶:Rust 2024 起,impl Trait 在返回位置默认捕获所有在作用域内的生命周期 (RPIT capture rules 2024)。因此在 2024 edition 下 fn evens(values: &[i32]) -> impl Iterator<Item = i32> 合法; 同一份代码在 2021 edition 下需要写成 -> impl Iterator<Item = i32> + '_。 想精确控制捕获哪些生命周期,可以用 1.82 起稳定的 use<> 语法: -> impl Iterator<Item = i32> + use<'_> 这类写法在需要「不捕获」时使用。



生成器式技巧:from_fn / successors / once / repeat

没有 yield 语法,但标准库给了三个构造迭代器的函数,覆盖绝大多数「手写生成器」的需求。

rust
fn main() {
    // 1) from_fn:每次 next() 调用一次闭包,闭包用捕获的外部状态推进
    let mut n = 0;
    let squares: Vec<u32> = std::iter::from_fn(|| {
        n += 1;
        if n <= 5 { Some(n * n) } else { None }
    }).collect();
    println!("{squares:?}");            // 输出:[1, 4, 9, 16, 25]

    // 2) successors:给定起点 + 「下一步」函数,天然表达递推序列
    let collatz: Vec<u64> = std::iter::successors(Some(27u64), |&x| {
        if x == 1 { None }
        else if x % 2 == 0 { Some(x / 2) }
        else { Some(3 * x + 1) }
    }).collect();
    println!("{} / 首项 {}", collatz.len(), collatz[0]);   // 输出:112 / 首项 27

    // 3) once / empty / repeat:构造「常量流」
    let single: Vec<i32> = std::iter::once(42).collect();
    let none: Vec<i32> = std::iter::empty().collect();
    let ones: Vec<i32> = std::iter::repeat(1).take(3).collect();
    let twos: Vec<i32> = std::iter::repeat_n(2, 3).collect();      // 1.82 起
    println!("{single:?} {none:?} {ones:?} {twos:?}");
    // 输出:[42] [] [1, 1, 1] [2, 2, 2]

    // once 常用于「把单个值接进管道」
    let all: Vec<i32> = std::iter::once(0).chain(1..=3).collect();
    println!("{all:?}");                // 输出:[0, 1, 2, 3]
}

collectVec<Vec<_>>(嵌套收集):

rust
fn main() -> Result<(), std::num::ParseIntError> {
    let text = "1 2 3\n4 5\n6";

    // 外层 collect 收集行,内层 collect 收集每行的数字
    let rows: Vec<Vec<i32>> = text
        .lines()
        .map(|line| line.split_whitespace().map(str::parse::<i32>).collect())
        .collect::<Result<Vec<Vec<i32>>, _>>()?;      // ? 把内层的 Result 短路出来
    println!("{rows:?}");                              // 输出:[[1, 2, 3], [4, 5], [6]]

    // 想「压平」成一层,把内层 collect 换成直接产出迭代器
    let flat: Vec<i32> = text
        .lines()
        .flat_map(|line| line.split_whitespace())
        .map(str::parse::<i32>)
        .collect::<Result<Vec<i32>, _>>()?;
    println!("{flat:?}");                              // 输出:[1, 2, 3, 4, 5, 6]
    Ok(())
}

惰性管道 vs for 循环:什么时候该退回循环?

判据用迭代器链for 循环
逻辑长度≤ 5~6 个阶段,每段一行超过 6 段、或每段逻辑复杂
控制流break / 无多层 continue、无提前 return需要 break 'outer、需要 return 提前退出
副作用纯转换,或副作用可用 for_each 表达多处副作用、日志与业务交织
错误处理每个元素返回 Result/Optioncollect/try_fold需要一个共享的可变状态、部分成功的中间态
可读性类型能一眼看出(Vec<i32>Vec<String>类型复杂到需要给每步写注释
性能通常与手写循环等价,甚至更好需要「边遍历边改」时反而必须用循环 + retain/drain

经验法则:先写 for 循环把逻辑跑通,再把它重写成链;如果重写后更难读, 就留在循环版本。 迭代器链的价值是「让读者一眼看到数据流向」,不是「显得高级」。

⚠️ 陷阱:把带 break 的循环硬改成链,常见错误是用 take_while 代替 break —— 但 take_while 只看元素本身,无法访问循环外的状态;而 scantry_fold 可以。 选错适配器会让语义从「提前退出」变成「过滤掉某个元素」。



借用型迭代器与「边遍历边改」

v.iter() 借走了 v

rust
fn main() {
    let v = vec![1, 2, 3];
    let it = v.iter();               // Iter<'_, i32> 内部持有 &[i32],等价于 &v 被借出
    println!("{}", it.len());        // 输出:3
    println!("{v:?}");               // 只读访问没问题(共享借用可以并存)
    // v.clear();                    // ❌ E0502:it 还活着,v 不能改
}

Iter<'a, T> 的类型里带着 'a,这个 'a 就是 &v 的生命周期。 只要迭代器还有可能被使用,v 就被共享借用,任何 &mut v 都会被拒绝。

E0502 的经典案例:迭代中修改集合

rust
// ❌ 此代码无法编译(E0502)
fn main() {
    let mut v = vec![1, 2, 3];
    for x in &v {
        if *x == 2 {
            v.push(9);               // 想边遍历边追加
        }
    }
}
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> src\main.rs:4:9
  |
3 |     for x in &v {
  |               - immutable borrow occurs here
4 |         if *x == 2 {
5 |             v.push(9);
  |             ^^^^^^^^^ mutable borrow occurs here
6 |         }
7 |     }
  |      - immutable borrow later used here

同样的错误在别处会换一张脸,但本质完全一样:

症状报错码本质
for x in &v { v.push(..); }E0502遍历持有共享借用,push 要独占借用
for x in &mut v { v.remove(0); }E0499iter_mut 已独占,remove 又要独占
for (k, _) in &map { map.insert(..); }E0502同上,哈希表也一样
let it = v.iter(); v.clear(); it.next();E0502迭代器的最后一次使用在 clear 之后

根本原因Vec 的迭代器是一个「指向堆缓冲区的游标」。 push/remove/clear 都可能重新分配或搬移缓冲区, 迭代器持有的指针立刻失效 —— 这是 C++ 里 vector 迭代器失效(iterator invalidation)的 同名问题,只是 Rust 把它变成了编译错误。

   迭代器 + 重新分配 = 悬垂游标

   it ──► [1][2][3]           v.push(9) 触发扩容
                              ┌──────────────────────┐
   it ──► (旧地址,已 free)   │ [1][2][3][9] 新地址  │
                              └──────────────────────┘
   ⇒ C++:UB;Java:ConcurrentModificationException;
     Python:RuntimeError: dictionary changed size during iteration;
     Rust:编译错误(最省钱的那种失败)

正解:retain / drain / mem::take / 先收集后修改

rust
use std::mem;

fn main() {
    // 正解 1:只是「按条件删元素」⇒ retain(一次遍历、原地压缩,O(n))
    let mut v = vec![1, 2, 3, 4, 5, 6];
    v.retain(|x| x % 2 == 0);
    println!("{v:?}");                         // 输出:[2, 4, 6]

    // 正解 2:需要「按条件改元素」⇒ iter_mut(不改变长度,借用安全)
    let mut v = vec![1, 2, 3];
    v.iter_mut().for_each(|x| *x *= 10);
    println!("{v:?}");                         // 输出:[10, 20, 30]

    // 正解 3:需要「边遍历边消费原集合」⇒ drain(拿走所有权的同时清空原 Vec)
    let mut v = vec!["a".to_string(), "b".to_string()];
    let mut out = Vec::new();
    for s in v.drain(..) {                     // 元素被移动出来,v 变成空 Vec
        out.push(s.to_uppercase());
    }
    println!("{out:?} / v 现在有 {} 个", v.len());     // 输出:["A", "B"] / v 现在有 0 个

    // 正解 4:需要「遍历 A 的同时往 B 里写」(B 就是 A 时)⇒ mem::take 换出空壳
    let mut v = vec![1, 2, 3];
    for x in mem::take(&mut v) {               // v 被换成一个空 Vec,可以自由写
        if x < 3 {
            v.push(x * 100);
        }
    }
    println!("{v:?}");                         // 输出:[100, 200]

    // 正解 5:先收集决策、再统一执行(最通用,代价是一次中间 Vec)
    let mut v = vec![1, 2, 3];
    let to_add: Vec<i32> = v.iter().filter(|&&x| x >= 2).map(|x| x * 3).collect();
    v.extend(to_add);
    println!("{v:?}");                         // 输出:[1, 2, 3, 6, 9]
}
需求正解复杂度说明
按条件删除元素retain(f)O(n)f: FnMut(&T) -> boolretain_mut&mut T(1.61 起)
按条件修改元素iter_mut().for_each(..)O(n)不改变长度
遍历并消费元素drain(range)O(n)返回迭代器,可复用原容量;Vec::drain 1.6 起
遍历并写入同一个变量mem::take(&mut v)O(1) 换出把容器换成 Default(通常是空),旧值拥有所有权
需要复杂决策collect 到临时 Vec,再统一改O(n) + 一次分配最通用、最好读;中间 Vec 可 with_capacity
按 key 删除哈希表项HashMap::retain(f)O(n)1.18 起,f: FnMut(&K, &mut V) -> bool
队列两端进出VecDeque两端 O(1)见「速查表」 速查表

⚠️ 陷阱mem::take(&mut v) 之后 vVec::default()容量是 0(不是保留原容量), 所以紧接着的大量 push 会重新分配。要保留容量用 let mut tmp = Vec::with_capacity(v.capacity()); std::mem::swap(&mut v, &mut tmp); 或者直接用 drain(..)(它保留容量)。

🚀 进阶drain 的返回值在drop才算「把剩余元素删干净」, 所以 let it = v.drain(..); 之后如果只是部分消费,剩余的照样会被删除。 想「取出来几个又放回去」,用 drain(..n) 拿前 n 个,或 Vec::split_off(n)



与其他语言的对照

容器对照表

需求PythonJS / TSJavaC++Rust
动态数组listArrayArrayList<T>std::vector<T>Vec<T>
哈希表 / 字典dictMap / 对象字面量HashMap<K,V>std::unordered_mapHashMap<K,V>
有序映射无(dict 保插入序)TreeMapstd::mapBTreeMap<K,V>
集合set / frozensetSetHashSet / TreeSetunordered_set / setHashSet / BTreeSet
双端队列collections.deque无(用数组)ArrayDequestd::dequeVecDeque
优先队列 / 堆heapq(最小堆)PriorityQueuepriority_queue(最大堆)BinaryHeap(最大堆,最小堆用 Reverse
字符串str(不可变)stringString(不可变)std::stringString(拥有)/ &str(借用)
字符串切片s[1:3]复制s.slice(1,3)(复制)s.substring(1,3)(复制)std::string_view(视图,易悬垂)&s[1..3](视图,编译期保证不悬垂)
越界访问IndexError(运行期)undefined / 静默IndexOutOfBoundsExceptionUBv[i] panic,v.get(i)Option
哈希表缺 key 取默认d[k] = d.get(k, 0) + 1m.get(k) ?? 0getOrDefault / mergem[k] 会自动插入 0*m.entry(k).or_insert(0) += 1
迭代中修改容器RuntimeError行为不定ConcurrentModificationExceptionUB编译错误(E0502
遍历顺序dict 保插入序Map 保插入序HashMap 不保证unordered_map 不保证HashMap 不保证;BTreeMap 按键升序
自定义 key 的要求__hash__ + __eq__任意值(SameValueZero)hashCode + equalsstd::hash 特化 + operator==#[derive(Hash, PartialEq, Eq)]

迭代器链对照

操作PythonJSJava StreamC++20 RangesRust
变换[f(x) for x in xs]xs.map(f)xs.stream().map(f)xs | views::transform(f)xs.iter().map(f)
过滤[x for x in xs if p(x)]xs.filter(p).filter(p)views::filter(p).filter(p)
变换 + 过滤合一生成器表达式 + ifflatMapmapMultitransform + filter.filter_map(f)
求和sum(xs)xs.reduce((a,b)=>a+b,0).mapToInt(..).sum()std::accumulate.sum::<i32>()
折叠functools.reduce(f, xs, init)reduce.reduce(init, f)std::accumulate.fold(init, f)
收集结果list(...)(立即返回数组).collect(Collectors.toList())std::vector(rng).collect::<Vec<_>>()
取前 n 个itertools.islice(xs, n)xs.slice(0, n).limit(n)views::take(n).take(n)
带下标enumerate(xs)xs.entries()IntStream.rangeviews::enumerate(C++23).enumerate()
相邻窗口zip(xs, xs[1:])手写循环手写循环views::adjacent<2>(C++23)xs.windows(2)
惰性求值生成器表达式惰性,列表推导立即map/filter 立即并新建数组✅ 惰性✅ 惰性✅ 惰性
短路找到next((x for x in xs if p(x)), None)xs.find(p).filter(p).findFirst()ranges::find_if.find(p)
是否全部满足all(p(x) for x in xs)xs.every(p).allMatch(p)ranges::all_of.all(p)
分组itertools.groupbyObject.groupByCollectors.groupingBy手写手写 entry().or_default().push(..)
链的中间容器列表推导会建新 listmap 建新数组不建(流式)不建(视图)不建(迭代器)

零成本抽象:单态化让迭代器「免费」

Rust 的泛型在编译期单态化(monomorphization):编译器为每个具体类型组合 生成一份专用代码。于是 v.iter().map(|x| x * 2).filter(|x| x > &3) 展开后是:

   source:
     v.iter().map(closure1).filter(closure2).collect::<Vec<_>>()

   monomorphization 之后(概念上):
     struct MapIter<'a> { inner: slice::Iter<'a, i32>, f: Closure1 }
     struct FilterIter<I> { inner: I, p: Closure2 }
     impl Iterator for FilterIter<MapIter<'a, Closure1>> { ... }

   内联之后:
     let mut out = Vec::with_capacity(v.len());
     for &x in v {                       // 一个循环搞定
         let y = x * 2;                  // closure1 内联
         if y > 3 { out.push(y); }       // closure2 内联
     }
语言迭代抽象的代价原因
Rust通常 0(与手写循环同汇编)单态化 + 内联,闭包是零大小类型,适配器结构体全部被优化掉
C++通常 0(模板同理)模板实例化 + 内联;但 std::function 会引入间接调用
Java每次 next() 一次虚调用 + 可能的装箱Stream 是接口,JIT 有时能内联但没保证
Python每个元素一个 Python 对象 + 解释器循环一切皆为对象,map/filter 也要调用 Python 函数
JSmap/filter 是内置(快),但会创建中间数组;回调也是函数调用V8 有优化,但语义上必须立刻产出数组
Gofor range 无抽象;想链式就得手写或用泛型函数没有方法链式适配器

代价在编译期与二进制体积:每种迭代器 + 闭包组合都会生成一份代码。 一条链上 5 个闭包,就是 5 个新类型。若一个泛型函数被 100 种类型实例化, 二进制会明显变大(这是 Rust 编译慢的重要原因之一)。

🧠 原理:「零成本抽象」的完整含义是:你不用的东西不付出代价,你用的东西不比手写差。 前半句指 Rust 没有 GC、没有运行期反射;后半句指迭代器/泛型在优化后与手写循环等价。 但它不承诺编译时间:抽象越多,单态化出的代码越多,编译越慢。

💡 对照:Java 的 Stream 需要「先 .stream().collect()」是因为它必须 从集合切换到流、再切回来;Rust 的 iter()/collect() 是同一件事的对称操作, 但 Vec、数组、&[T]Range 全都是迭代器来源,不需要中间适配层。



常见坑与编译错误

for x in v 移动了 v

rust
// ❌ 此代码无法编译(E0382)
fn main() {
    let v = vec![String::from("a")];
    for s in v {                     // v 被 into_iter() 消费
        println!("{s}");
    }
    println!("{}", v.len());         // 这里 v 已经没了
}
error[E0382]: borrow of moved value: `v`
 --> src\main.rs:6:20
  |
3 |     let v = vec![String::from("a")];
  |         - move occurs because `v` has type `Vec<String>`,
  |           which does not implement the `Copy` trait
4 |     for s in v {
  |              - `v` moved due to this implicit call to `.into_iter()`
...
6 |     println!("{}", v.len());
  |                    ^ value borrowed here after move

修法:遍历用 &v(要读)或 &mut v(要改);确实要消费就接受 v 消失。 元素是 Copy 类型时也别偷懒 —— for x in vVec<i32> 同样会移动 v, 因为 Vec<i32> 本身不是 Copy

collect 类型推断失败需要 turbofish

rust
// ❌ 此代码无法编译(E0283)
fn main() {
    let v = (1..=3).collect();       // 收集成什么?Vec?HashSet?String?
    println!("{v:?}");
}
error[E0283]: type annotations needed
 --> src\main.rs:3:9
  |
3 |     let v = (1..=3).collect();       // 收集成什么?Vec?HashSet?String?
  |         ^           ------- type must be known at this point
  |
  = note: the type must implement `FromIterator<i32>`
note: required by a bound in `collect`
help: consider giving `v` an explicit type
  |
3 |     let v: Vec<_> = (1..=3).collect();       // 收集成什么?Vec?HashSet?String?
  |          ++++++++

目标类型完全没线索时报 E0283;有时也会报 E0282。 两者含义一样:「我看不出你要收集成什么类型」,处方都是加类型标注或 turbofish。

三种修法,按可读性排序:

rust
use std::collections::HashSet;

fn main() {
    let a: Vec<i32> = (1..=3).collect();                       // 1) 变量标注类型
    let b = (1..=3).collect::<Vec<i32>>();                     // 2) turbofish(管道在中间时常用)
    let c: HashSet<i32> = (1..=3).collect();                   // 3) 目标类型本身就在约束里

    println!("{a:?} {b:?} {:?}", {
        let mut v: Vec<i32> = c.into_iter().collect();
        v.sort();
        v
    });
    // 输出:[1, 2, 3] [1, 2, 3] [1, 2, 3]
}

⚠️ 陷阱collect::<Result<Vec<_>, _>>() 里两个 _ 都是必需的: 第一个让元素类型由迭代器推断,第二个让错误类型由闭包推断。 只写 collect::<Result<Vec<i32>, _>>() 也行,但错误类型必须能从上下文推断出来。

HashMap 的 key 是 String 时的所有权处理

rust
use std::collections::HashMap;

fn main() {
    let mut map: HashMap<String, i32> = HashMap::new();

    // 坑 1:为插入而 clone key
    let key = String::from("count");
    map.insert(key.clone(), 1);

    // 坑 2:想「找到就改」却先 get 再 insert,中间的借用已经结束,语义也对但效率差
    if map.contains_key(&key) {
        let v = map.get_mut(&key).expect("刚确认存在");
        *v += 1;
    }

    // 正解:entry 一次查找,且不用 clone key(key 只在真正需要插入时才被消费)
    map.entry(key.clone()).and_modify(|v| *v += 1).or_insert(0);

    // 查询时用 &str 就够了:String: Borrow<str>
    println!("{:?}", map.get("count"));            // 输出:Some(&2)
    println!("{map:?}");
}

如果要避免为每个 key 分配 String

方案适用代价
HashMap<&str, V>key 来自生命周期足够长的数据(如整个输入文本)生命周期参数传染到结构体
HashMap<Box<str>, V>需要拥有但 String 的 capacity 用不上String 等价
HashMap<u64, V>(先做完美哈希/ID)高频查询、key 集合固定需要自己做 ID 映射
HashMap<SmolStr, V>cargo add smol_str@0.3大量短 key第三方依赖

性能与语义陷阱清单

症状修法
s.chars().nth(i)循环里用 ⇒ O(n²)let v: Vec<char> = s.chars().collect();,之后 v[i] 是 O(1)
Vec::remove(0) / insert(0, x)当队列用 ⇒ O(n²)VecDequepop_front/push_back
Vec 不预分配百万级 push 触发多次搬移 + 峰值内存翻倍Vec::with_capacity(n)
or_insert(compute())默认值的构造函数每次都被调用or_insert_with(|| compute()) / or_default()
HashMap 迭代顺序测试偶尔失败、输出顺序每次不同BTreeMap,或 collectsort
binary_search 未排序不 panic,静默返回错误结果sort(),或改用 iter().find(..)
sort_by 的比较器非严格弱序可能 panic(user-provided comparison function does not correctly implement a total order)或排序结果错乱保证 a.cmp(b)b.cmp(a)a.cmp(c) 三条性质;别用 <=/>=,别在比较里 unwrapNone
sort_by 里返回 Ordering::Less/Greater 混用同上a.partial_cmp(b).unwrap_or(Ordering::Equal)f64::total_cmp(1.62 起)
sort_by_key 的 key 借用元素本身生命周期报错sort_bysort_by_cached_key
windows(0) / chunks(0)运行期 panicassert!(n > 0)
sum::<i32>() 溢出debug panic、release 静默回绕i64,或 try_fold + checked_add
map 链忘记消费unused_must_use 警告但程序「什么都没发生」收尾加 .collect() / for_each / 赋值;CI 用 -D warnings
HashMap<f64, V>E0277: f64: Eq is not satisfied / Hash is not implemented for f64转定点整数,或用 ordered_float
遍历时修改E0502 / E0499retain / drain / mem::take(见「正解:retain / drain / mem::take / 先收集后修改」)
&Vec<T> / &String 参数clippy::ptr_arg 警告、调用方受限&[T] / &str

sort_by 的严格弱序(strict weak ordering)值得单独强调 —— 它是排序正确性的前提

rust
fn main() {
    let mut v = vec![3, 1, 2];

    // ✅ 正确:用 cmp 得到一个真正的全序
    v.sort_by(|a, b| a.cmp(b));
    println!("{v:?}");                       // 输出:[1, 2, 3]

    // ✅ 浮点:total_cmp 给全序(-0.0 < 0.0,NaN 被排在最后)
    let mut f = vec![2.5, f64::NAN, 1.0];
    f.sort_by(|a, b| a.total_cmp(b));
    println!("{f:?}");                       // 输出:[1.0, 2.5, NaN]

    // ❌ 反面:比较器写成 a >= b 会破坏反对称性,1.81+ 的排序实现会主动 panic
    // v.sort_by(|a, b| if a >= b { Ordering::Greater } else { Ordering::Less });
}

⚠️ 陷阱sort_by 的比较器必须满足严格弱序,否则 1.81 起的新排序实现 (driftsort / ipnsort)会检测到并 panic: user-provided comparison function does not correctly implement a total order。 在这之前,违反契约是「排序结果错乱」这种更难查的 bug —— 所以这其实是好事。



速查表

我想要……写法备注
建可增长数组Vec::new() / vec![1,2,3] / Vec::with_capacity(n)已知规模就预分配
增删push(x) / pop() / insert(i, x) / remove(i)前两个均摊 O(1),后两个 O(n),别当队列用
安全取值get(i) / first() / last()Option<&T>不 panic;v[i] 越界会 panic
取切片&v[a..b] / &v[..] / &mut v[a..b]零拷贝视图
函数参数&[T] / &mut [T] / &str不要 &Vec<T> / &String
分组 / 窗口v.chunks(n) / v.windows(n)切片方法,n > 0
排序 / 查找v.sort() / v.sort_unstable() / v.binary_search(&x)binary_search 要求已排序
按条件删 / 拿走元素v.retain(|x| ...) / v.drain(..) / std::mem::take(&mut v)drain 保留容量
词频 / 分组累加*m.entry(k).or_insert(0) += 1一次查找,别用 contains_key + insert
缺 key 才插 / 存在才改.or_insert(v) / .or_insert_with(f) / .or_default() / .and_modify(f).or_insert(v)_with 才惰性求值
有序遍历 / 范围查询BTreeMap + range(a..b)HashMap 做不到
去重HashSet::insert 返回 bool!insert(x) 即重复
三种遍历&v / &mut v / v对应 iter / iter_mut / into_iter
收集 / 统计 / 累加collect::<Vec<_>>() / collect::<Result<Vec<_>,_>>() / sum::<i32>() / count() / fold / reduce / try_fold推断不出就 turbofish;try_fold 可短路
队列 / 堆 / 迭代器返回VecDeque::push_back+pop_front / BinaryHeap(最小堆套 Reverse) / -> impl Iterator<Item = T>BinaryHeap 是最大堆;size_hint 顺手实现


内容以 rustc 1.98.1 · Rust 2024 edition 为基准