LINQ
LINQ
本节在“序列查询与转换”的语境下讨论 LINQ,主要关注 IEnumerable / IEnumerable<T>
及常见集合(列表、集合、字典等)。
IEnumerable<T>
Rust 中最接近 IEnumerable<T> 的抽象是 IntoIterator。
就像 .NET 中 IEnumerable<T>.GetEnumerator() 会返回 IEnumerator<T>,
Rust 中 IntoIterator::into_iter 会返回 Iterator。
两门语言也都提供了遍历语法糖:
- C# 使用
foreach - Rust 使用
for
C# 示例:
using System;
using System.Text;
var values = new[] { 1, 2, 3, 4, 5 };
var output = new StringBuilder();
foreach (var value in values)
{
if (output.Length > 0)
output.Append(", ");
output.Append(value);
}
Console.Write(output); // Prints: 1, 2, 3, 4, 5Rust 示例:
use std::fmt::Write;
fn main() {
let values = [1, 2, 3, 4, 5];
let mut output = String::new();
for value in values {
if output.len() > 0 {
output.push_str(", ");
}
_ = write!(output, "{value}");
}
println!("{output}"); // Prints: 1, 2, 3, 4, 5
}for 在 Rust 中大致可展开为:
use std::fmt::Write;
fn main() {
let values = [1, 2, 3, 4, 5];
let mut output = String::new();
let mut iter = values.into_iter();
while let Some(value) = iter.next() {
if output.len() > 0 {
output.push_str(", ");
}
_ = write!(output, "{value}");
}
println!("{output}");
}需要特别注意:Rust 的所有权规则会影响迭代行为。
当对 Vec<T> 做 for value in values 时,默认会移动每个元素所有权;若后续还要继续使用
原集合,应写成 for value in &values(按引用迭代)。
fn main() {
let values = vec![1, 2, 3, 4, 5];
let mut sum = 0;
for value in &values {
sum += value;
}
println!("sum = {sum}");
let mut max = None;
for value in &values {
if let Some(some_max) = max {
if value > some_max {
max = Some(value)
}
} else {
max = Some(value)
}
}
println!("max = {max:?}");
}可通过 Drop 观察“按值迭代会逐项被销毁”的行为:
struct Int(i32);
impl Drop for Int {
fn drop(&mut self) {
println!("{} dropped", self.0)
}
}
fn main() {
let values = [Int(1), Int(2), Int(3), Int(4), Int(5)];
let mut sum = 0;
for value in values {
sum += value.0;
}
println!("sum = {sum}");
}另见:
操作符
LINQ 操作符本质是可链式调用的扩展方法。Rust 没有 C# 的查询表达式语法(from/where/select),
但迭代器适配器(adapters)提供了非常接近的方法链能力。
在 C# 中,把命令式循环改写为 LINQ 往往能提升可读性与组合性,但有时会有性能权衡;
在 Rust 中,迭代器链通常可被编译器优化到接近手写循环的性能,因此在实际代码中非常常见。
下表给出常见 LINQ 方法与 Rust 近似映射:
| .NET | Rust | Note |
|---|---|---|
Aggregate | reduce | 见注释 1 |
Aggregate | fold | 见注释 1 |
All | all | |
Any | any | |
Concat | chain | |
Count | count | |
ElementAt | nth | |
GroupBy | - | |
Last | last | |
Max | max | |
Max | max_by | |
MaxBy | max_by_key | |
Min | min | |
Min | min_by | |
MinBy | min_by_key | |
Reverse | rev | |
Select | map | |
Select | enumerate | |
SelectMany | flat_map | |
SelectMany | flatten | |
SequenceEqual | eq | |
Single | find | |
SingleOrDefault | try_find | |
Skip | skip | |
SkipWhile | skip_while | |
Sum | sum | |
Take | take | |
TakeWhile | take_while | |
ToArray | collect | 见注释 2 |
ToDictionary | collect | 见注释 2 |
ToList | collect | 见注释 2 |
Where | filter | |
Zip | zip |
- 不带 seed 的
Aggregate接近reduce;带 seed 的Aggregate接近fold。 collect可把迭代器收集为任意实现FromIterator的类型; 有时需显式标注目标类型(如collect::<Vec<_>>(),即 turbofish)。
示例对比:
var result =
Enumerable.Range(0, 10)
.Where(x => x % 2 == 0)
.SelectMany(x => Enumerable.Range(0, x))
.Aggregate(0, (acc, x) => acc + x);
Console.WriteLine(result); // 50let result = (0..10)
.filter(|x| x % 2 == 0)
.flat_map(|x| (0..x))
.fold(0, |acc, x| acc + x);
println!("{result}"); // 50延迟执行(惰性)
LINQ 中大量操作符都是延迟执行:只有在真正枚举结果时才发生计算。
Rust 迭代器同样遵循这种 惰性 语义,也天然支持流式处理。
这使得两者都能表达“无限序列”,再通过 Take/take 限制输出。
C#:
foreach (var x in InfiniteRange().Take(5))
Console.Write($"{x} "); // Prints "0 1 2 3 4"
IEnumerable<int> InfiniteRange()
{
for (var i = 0; ; ++i)
yield return i;
}Rust:
for value in (0..).take(5) {
print!("{value} "); // Prints "0 1 2 3 4"
}迭代器方法(yield)
C# 的 yield 可快速实现迭代器方法(返回 IEnumerable<T> 或 IEnumerator<T>),
由编译器生成状态机实现。
Rust 对应能力通常通过迭代器适配器与组合来实现;语言级 coroutines 在撰写本文时仍属不稳定特性。