8. Functional vs. Imperative: When Elegance Wins (and When It Doesn’t)
难度: 🟡 中级 | 时间: 2-3 小时 | 前置要求: 第 7 章 — 闭包
Rust 在函数式和命令式风格之间提供真正的对等。不像 Haskell(强制函数式)或 C(默认命令式),Rust 让你选择 —— 正确的选择取决于你要表达什么。本章培养正确判断的能力。
核心原则: 当你通过管道转换数据时函数式风格发光。当你管理带有副作用的状态转换时命令式风格发光。大多数真实代码两者都有,技能在于知道边界落在哪里。
8.1 你不知道你需要的组合器
许多 Rust 开发者这样写:
#![allow(unused)]
fn main() {
let value = if let Some(x) = maybe_config() {
x
} else {
default_config()
};
process(value);
}
当他们可以这样写:
#![allow(unused)]
fn main() {
process(maybe_config().unwrap_or_else(default_config));
}
或这个常见模式:
#![allow(unused)]
fn main() {
let display_name = if let Some(name) = user.nickname() {
name.to_uppercase()
} else {
"ANONYMOUS".to_string()
};
}
也就是:
#![allow(unused)]
fn main() {
let display_name = user.nickname()
.map(|n| n.to_uppercase())
.unwrap_or_else(|| "ANONYMOUS".to_string());
}
函数式版本不仅更短 —— 它告诉你什么在发生(转换,然后默认),而不必追踪控制流。if let 版本让你阅读分支来找出两条路径最终到达同一地方。
Option 组合器家族
这里是心理模型:Option<T> 是一个元素或空的集合。Option 上的每个组合器都有集合操作的类比。
| 你写… | 替代… | 传达什么 |
|---|---|---|
opt.unwrap_or(default) | if let Some(x) = opt { x } else { default } | “使用此值或回退” |
opt.unwrap_or_else(|| expensive()) | if let Some(x) = opt { x } else { expensive() } | 相同,但默认是惰性的 |
opt.map(f) | match opt { Some(x) => Some(f(x)), None => None } | “转换内部,传播缺失” |
opt.and_then(f) | match opt { Some(x) => f(x), None => None } | “链接可能失败的操作” (flatmap) |
opt.filter(|x| pred(x)) | match opt { Some(x) if pred(&x) => Some(x), _ => None } | “仅当通过时保留” |
opt.zip(other) | if let (Some(a), Some(b)) = (opt, other) { Some((a,b)) } else { None } | “两者或都不” |
opt.or(fallback) | if opt.is_some() { opt } else { fallback } | “第一个可用的” |
opt.or_else(|| try_another()) | if opt.is_some() { opt } else { try_another() } | “按顺序尝试” |
opt.map_or(default, f) | if let Some(x) = opt { f(x) } else { default } | “转换或默认” —— 一行 |
opt.map_or_else(default_fn, f) | if let Some(x) = opt { f(x) } else { default_fn() } | 相同,两边都是闭包 |
opt? | match opt { Some(x) => x, None => return None } | “向上传播缺失” |
Result 组合器家族
相同模式适用于 Result<T, E>:
| 你写… | 替代… | 传达什么 |
|---|---|---|
res.map(f) | match res { Ok(x) => Ok(f(x)), Err(e) => Err(e) } | 转换成功路径 |
res.map_err(f) | match res { Ok(x) => Ok(x), Err(e) => Err(f(e)) } | 转换错误 |
res.and_then(f) | match res { Ok(x) => f(x), Err(e) => Err(e) } | 链接可能失败的操作 |
res.unwrap_or_else(|e| default(e)) | match res { Ok(x) => x, Err(e) => default(e) } | 从错误恢复 |
res.ok() | match res { Ok(x) => Some(x), Err(_) => None } | “我不关心错误” |
res? | match res { Ok(x) => x, Err(e) => return Err(e.into()) } | 向上传播错误 |
何时 if let 更好
组合器在以下情况失败:
- 你需要在
Some分支中多条语句。 5 行的 map 闭包比 5 行的if let更糟。 - 控制流是重点。
if let Some(connection) = pool.try_get() { /* 使用它 */ } else { /* 日志、重试、警报 */ }—— 两个分支是真正不同的代码路径,不是转换或默认。 - 副作用主导。 如果两个分支都做 I/O 且有不同的错误处理,组合器版本模糊了重要的差异。
经验法则: 如果 else 分支产生与 Some 分支相同的类型且主体是短表达式,使用组合器。如果分支做 fundamentally 不同的事情,使用 if let 或 match。
8.2 Bool 组合器:.then() 和 .then_some()
另一个比应该更常见的模式:
#![allow(unused)]
fn main() {
let label = if is_admin {
Some("ADMIN")
} else {
None
};
}
Rust 1.62+ 给你:
#![allow(unused)]
fn main() {
let label = is_admin.then_some("ADMIN");
}
或带计算值:
#![allow(unused)]
fn main() {
let permissions = is_admin.then(|| compute_admin_permissions());
}
这在链中特别强大:
#![allow(unused)]
fn main() {
// 命令式
let mut tags = Vec::new();
if user.is_admin { tags.push("admin"); }
if user.is_verified { tags.push("verified"); }
if user.score > 100 { tags.push("power-user"); }
// 函数式
let tags: Vec<&str> = [
user.is_admin.then_some("admin"),
user.is_verified.then_some("verified"),
(user.score > 100).then_some("power-user"),
]
.into_iter()
.flatten()
.collect();
}
函数式版本使模式明确:“从条件元素构建列表。“命令式版本让你阅读每个 if 来确认它们都做相同的事情(push 标签)。
8.3 迭代器链 vs 循环:决策框架
第 7 章展示了机制。本节培养判断力。
何时迭代器赢
数据管道 —— 通过一系列步骤转换集合:
#![allow(unused)]
fn main() {
// 命令式:8 行,2 个可变变量
let mut results = Vec::new();
for item in inventory {
if item.category == Category::Server {
if let Some(temp) = item.last_temperature() {
if temp > 80.0 {
results.push((item.id, temp));
}
}
}
}
// 函数式:6 行,0 个可变变量,一个管道
let results: Vec<_> = inventory.iter()
.filter(|item| item.category == Category::Server)
.filter_map(|item| item.last_temperature().map(|t| (item.id, t)))
.filter(|(_, temp)| *temp > 80.0)
.collect();
}
函数式版本赢因为:
- 每个 filter 独立可读
- 无
mut—— 数据单向流动 - 你可以添加/删除/重新排序管道阶段而无需重构
- LLVM 内联迭代器适配器到与循环相同的机器码
聚合 —— 从集合计算单个值:
#![allow(unused)]
fn main() {
// 命令式
let mut total_power = 0.0;
let mut count = 0;
for server in fleet {
total_power += server.power_draw();
count += 1;
}
let avg = total_power / count as f64;
// 函数式
let (total_power, count) = fleet.iter()
.map(|s| s.power_draw())
.fold((0.0, 0usize), |(sum, n), p| (sum + p, n + 1));
let avg = total_power / count as f64;
}
或如果你只需要总和更简单:
#![allow(unused)]
fn main() {
let total: f64 = fleet.iter().map(|s| s.power_draw()).sum();
}
何时循环赢
提前退出带复杂状态:
#![allow(unused)]
fn main() {
// 这清晰且直接
let mut best_candidate = None;
for server in fleet {
let score = evaluate(server);
if score > threshold {
if server.is_available() {
best_candidate = Some(server);
break; // 找到一个 —— 立即停止
}
}
}
// 函数式版本是紧张的
let best_candidate = fleet.iter()
.filter(|s| evaluate(s) > threshold)
.find(|s| s.is_available());
}
等等 —— 那个函数式版本实际上相当干净。让我们尝试一个它真正失败的情况:
同时构建多个输出:
#![allow(unused)]
fn main() {
// 命令式:清晰,每个分支做不同的事情
let mut warnings = Vec::new();
let mut errors = Vec::new();
let mut stats = Stats::default();
for event in log_stream {
match event.severity {
Severity::Warn => {
warnings.push(event.clone());
stats.warn_count += 1;
}
Severity::Error => {
errors.push(event.clone());
stats.error_count += 1;
if event.is_critical() {
alert_oncall(&event);
}
}
_ => stats.other_count += 1,
}
}
// 函数式版本:强制、笨拙,没人想读这个
let (warnings, errors, stats) = log_stream.iter().fold(
(Vec::new(), Vec::new(), Stats::default()),
|(mut w, mut e, mut s), event| {
match event.severity {
Severity::Warn => { w.push(event.clone()); s.warn_count += 1; }
Severity::Error => {
e.push(event.clone()); s.error_count += 1;
if event.is_critical() { alert_oncall(event); }
}
_ => s.other_count += 1,
}
(w, e, s)
},
);
}
fold 版本更长、更难读,并且有可变性(mut 解构累加器)。循环赢因为:
- 多个输出同时构建
- 副作用(警报)混合到逻辑中
- 分支体是语句,不是表达式
带 I/O 的状态机:
#![allow(unused)]
fn main() {
// 读取 token 的解析器 —— 循环就是算法
let mut state = ParseState::Start;
loop {
let token = lexer.next_token()?;
state = match state {
ParseState::Start => match token {
Token::Keyword(k) => ParseState::GotKeyword(k),
Token::Eof => break,
_ => return Err(ParseError::UnexpectedToken(token)),
},
ParseState::GotKeyword(k) => match token {
Token::Ident(name) => ParseState::GotName(k, name),
_ => return Err(ParseError::ExpectedIdentifier),
},
// ...更多状态
};
}
}
没有函数式等价物更干净。带 match state 的循环是状态机的自然表达。
决策流程图
flowchart TB
START{你在做什么?}
START -->|"转换集合<br/>到另一个集合"| PIPE[使用迭代器链]
START -->|"从集合计算<br/>单个值"| AGG{多复杂?}
START -->|"从一次传递<br/>多个输出"| LOOP[使用 for 循环]
START -->|"带 I/O 或<br/>副作用的状态机"| LOOP
START -->|"一个 Option/Result<br/>转换 + 默认"| COMB[使用组合器]
AGG -->|"总和、计数、最小、最大"| BUILTIN["使用 .sum()、.count()、<br/>.min()、.max()"]
AGG -->|"自定义归约"| FOLD{累加器有可变性<br/>或副作用?}
FOLD -->|"否"| FOLDF["使用 .fold()"]
FOLD -->|"是"| LOOP
style PIPE fill:#d4efdf,stroke:#27ae60,color:#000
style COMB fill:#d4efdf,stroke:#27ae60,color:#000
style BUILTIN fill:#d4efdf,stroke:#27ae60,color:#000
style FOLDF fill:#d4efdf,stroke:#27ae60,color:#000
style LOOP fill:#fef9e7,stroke:#f1c40f,color:#000
侧边栏:Scoped 可变性 —— 内部命令式,外部函数式
Rust 块是表达式。这让你将可变性限制在构建阶段并将结果不可变地绑定:
#![allow(unused)]
fn main() {
use rand::random;
let samples = {
let mut buf = Vec::with_capacity(10);
while buf.len() < 10 {
let reading: f64 = random();
buf.push(reading);
if random::<u8>() % 3 == 0 { break; } // 随机提前停止
}
buf
};
// samples 是不可变的 —— 包含 1 到 10 个元素
}
内部 buf 仅在块内可变。一旦块产生,外部绑定 samples 是不可变的,编译器将拒绝任何后续的 samples.push(...)。
为什么不是迭代器链? 你可能尝试:
#![allow(unused)]
fn main() {
let samples: Vec<f64> = std::iter::from_fn(|| Some(random()))
.take(10)
.take_while(|_| random::<u8>() % 3 != 0)
.collect();
}
但 take_while 排除 失败谓词的元素,产生 0 到 9 个元素而不是命令式版本提供的至少一个保证。你可以用 scan 或 chain 解决,但命令式版本更清晰。
scoped 可变性真正赢的情况:
| 场景 | 为什么迭代器挣扎 |
|---|---|
排序然后冻结 (sort_unstable() + dedup()) | 两者都返回 () —— 无可链接输出(如果可用 itertools 提供 .sorted().dedup()) |
| 有状态的终止(停止在与数据无关的条件) | take_while drop 边界元素 |
| 多步骤结构体填充(从不同来源逐字段) | 无自然的单个管道 |
诚实的校准: 对于大多数集合构建任务,迭代器链或 itertools 是首选。当构建逻辑有分支、提前退出、或不映射到单个管道的原位可变性时使用 scoped 可变性。模式的真正价值是教导可变性作用域可以比变量生命周期更小 —— 一个 Rust 基础,让来自 C++、C# 和 Python 的开发者惊讶。
8.4 ? 操作符:函数式与命令式的融合
? 操作符是 Rust 两种风格最优雅的综合。它本质上是 .and_then() 与提前返回的结合:
#![allow(unused)]
fn main() {
// 这个 and_then 链...
fn load_config() -> Result<Config, Error> {
read_file("config.toml")
.and_then(|contents| parse_toml(&contents))
.and_then(|table| validate_config(table))
.and_then(|valid| Config::from_validated(valid))
}
// ...等价于这个
fn load_config() -> Result<Config, Error> {
let contents = read_file("config.toml")?;
let table = parse_toml(&contents)?;
let valid = validate_config(table)?;
Config::from_validated(valid)
}
}
两者在精神上都是函数式的(它们自动传播错误),但 ? 版本给你命名的中间变量,这在以下情况重要:
- 你需要稍后再次使用
contents - 你想每步添加
.context("while parsing config")? - 你在调试并想检查中间值
反模式: 当 ? 可用时长 .and_then() 链。如果链中的每个闭包都是 |x| next_step(x),你重新发明了 ? 而没有可读性。
何时 .and_then() 比 ? 好:
#![allow(unused)]
fn main() {
// 在 Option 内部转换,无提前返回
let port: Option<u16> = config.get("port")
.and_then(|v| v.parse::<u16>().ok())
.filter(|&p| p > 0 && p < 65535);
}
你不能在这里使用 ? 因为没有封闭函数可返回 —— 你在构建 Option,不是传播它。
8.5 集合构建:collect() vs Push 循环
collect() 比大多数开发者意识到的更强大:
收集到 Result
#![allow(unused)]
fn main() {
// 命令式:解析列表,在第一个错误失败
let mut numbers = Vec::new();
for s in input_strings {
let n: i64 = s.parse().map_err(|_| Error::BadInput(s.clone()))?;
numbers.push(n);
}
// 函数式:收集到 Result<Vec<_>, _>
let numbers: Vec<i64> = input_strings.iter()
.map(|s| s.parse::<i64>().map_err(|_| Error::BadInput(s.clone())))
.collect::<Result<_, _>>()?;
}
collect::<Result<Vec<_>, _>>() 技巧有效因为 Result 实现 FromIterator。它在第一个 Err 短路,就像带 ? 的循环。
收集到 HashMap
#![allow(unused)]
fn main() {
// 命令式
let mut index = HashMap::new();
for server in fleet {
index.insert(server.id.clone(), server);
}
// 函数式
let index: HashMap<_, _> = fleet.into_iter()
.map(|s| (s.id.clone(), s))
.collect();
}
收集到 String
#![allow(unused)]
fn main() {
// 命令式
let mut csv = String::new();
for (i, field) in fields.iter().enumerate() {
if i > 0 { csv.push(','); }
csv.push_str(field);
}
// 函数式
let csv = fields.join(",");
// 或对于更复杂格式化:
let csv: String = fields.iter()
.map(|f| format!("\"{f}\""))
.collect::<Vec<_>>()
.join(",");
}
何时循环版本赢
collect() 分配新集合。如果你原位修改,循环更清晰和更高效:
#![allow(unused)]
fn main() {
// 原位更新 —— 无更好的函数式等价物
for server in &mut fleet {
if server.needs_refresh() {
server.refresh_telemetry()?;
}
}
}
函数式版本需要 .iter_mut().for_each(|s| { ... }),这只是带额外语法的循环。
8.6 模式匹配作为函数分发
Rust 的 match 是大多数开发者命令式使用的函数式结构。这里是函数式视角:
Match 作为查找表
#![allow(unused)]
fn main() {
// 命令式思维:"检查每个情况"
fn status_message(code: StatusCode) -> &'static str {
if code == StatusCode::OK { "Success" }
else if code == StatusCode::NOT_FOUND { "Not found" }
else if code == StatusCode::INTERNAL { "Server error" }
else { "Unknown" }
}
// 函数式思维:"从定义域映射到值域"
fn status_message(code: StatusCode) -> &'static str {
match code {
StatusCode::OK => "Success",
StatusCode::NOT_FOUND => "Not found",
StatusCode::INTERNAL => "Server error",
_ => "Unknown",
}
}
}
match 版本不仅是风格 —— 编译器验证穷尽性。添加新变体,每个不处理它的 match 变成编译错误。if/else 链静默落入默认。
Match + 解构作为管道
#![allow(unused)]
fn main() {
// 解析命令 —— 每个 arm 提取和转换
fn execute(cmd: Command) -> Result<Response, Error> {
match cmd {
Command::Get { key } => db.get(&key).map(Response::Value),
Command::Set { key, value } => db.set(key, value).map(|_| Response::Ok),
Command::Delete { key } => db.delete(&key).map(|_| Response::Ok),
Command::Batch(cmds) => cmds.into_iter()
.map(execute)
.collect::<Result<Vec<_>, _>>()
.map(Response::Batch),
}
}
}
每个 arm 是返回相同类型的表达式。这是作为函数分发的模式匹配 —— match arm 本质上是按枚举变体索引的函数表。
8.7 在自定义类型上链接方法
函数式风格扩展到标准库类型之外。Builder 模式和流畅 APIs 是伪装的函数式编程:
#![allow(unused)]
fn main() {
// 这是在你自己类型上的组合器链
let query = QueryBuilder::new("servers")
.filter("status", Eq, "active")
.filter("rack", In, &["A1", "A2", "B1"])
.order_by("temperature", Desc)
.limit(50)
.build();
}
关键洞察: 如果你的类型有接受 self 并返回 Self(或转换类型)的方法,你构建了组合器。相同的函数式/命令式判断适用:
#![allow(unused)]
fn main() {
// 好:可链接因为每步是简单转换
let config = Config::default()
.with_timeout(Duration::from_secs(30))
.with_retries(3)
.with_tls(true);
// 坏:可链接但链在做太多不相关的事情
let result = processor
.load_data(path)? // I/O
.validate() // 纯
.transform(rule_set) // 纯
.save_to_disk(output)? // I/O
.notify_downstream()?; // 副作用
// 更好:分离纯管道和 I/O 书端
let data = load_data(path)?;
let processed = data.validate().transform(rule_set);
save_to_disk(output, &processed)?;
notify_downstream()?;
}
当链混合纯转换与 I/O 时失败。读者不能分辨哪些调用可能失败、哪些有副作用、实际数据转换发生在哪里。
8.8 性能:它们相同
常见误解:“函数式风格更慢因为所有闭包和分配。”
在 Rust 中,迭代器链编译到与手写循环相同的机器码。 LLVM 内联闭包调用,消除迭代器适配器结构体,并且经常产生相同的汇编。这叫零成本抽象,它不是愿望 —— 它是测量的。
#![allow(unused)]
fn main() {
// 这些在 release 构建中产生相同的汇编:
// 函数式
let sum: i64 = (0..1000).filter(|n| n % 2 == 0).map(|n| n * n).sum();
// 命令式
let mut sum: i64 = 0;
for n in 0..1000 {
if n % 2 == 0 {
sum += n * n;
}
}
}
唯一的例外: .collect() 分配。如果你用中间集合链接 .map().collect().iter().map().collect(),你在支付循环版本避免的分配。修复:通过直接链接适配器消除中间 collect,或者如果你需要中间集合用于其他原因使用循环。
8.9 味觉测试:转换目录
这里是常见“我写了 6 行但有一行“模式的参考表:
| 命令式模式 | 函数式等价物 | 何时偏好函数式 |
|---|---|---|
if let Some(x) = opt { f(x) } else { default } | opt.map_or(default, f) | 两边短表达式 |
if let Some(x) = opt { Some(g(x)) } else { None } | opt.map(g) | 总是 —— 这就是 map 的目的 |
if condition { Some(x) } else { None } | condition.then_some(x) | 总是 |
if condition { Some(compute()) } else { None } | condition.then(compute) | 总是 |
match opt { Some(x) if pred(x) => Some(x), _ => None } | opt.filter(pred) | 总是 |
for x in iter { if pred(x) { result.push(f(x)); } } | iter.filter(pred).map(f).collect() | 当管道在一屏内可读 |
if a.is_some() && b.is_some() { Some((a?, b?)) } | a.zip(b) | 总是 —— .zip() 正是这个 |
match (a, b) { (Some(x), Some(y)) => x + y, _ => 0 } | a.zip(b).map(|(x,y)| x + y).unwrap_or(0) | 判断调用 —— 取决于复杂度 |
iter.map(f).collect::<Vec<_>>()[0] | iter.map(f).next().unwrap() | 不要为一个元素分配 Vec |
let mut v = vec; v.sort(); v | { let mut v = vec; v.sort(); v } | Rust 在 std 中没有 .sorted()(使用 itertools) |
8.10 反模式
过度函数式化:没人能读的 5 层深链
#![allow(unused)]
fn main() {
// 这不优雅。这是谜题。
let result = data.iter()
.filter_map(|x| x.metadata.as_ref())
.flat_map(|m| m.tags.iter())
.filter(|t| t.starts_with("env:"))
.map(|t| t.strip_prefix("env:").unwrap())
.filter(|env| allowed_envs.contains(env))
.map(|env| env.to_uppercase())
.collect::<HashSet<_>>()
.into_iter()
.sorted()
.collect::<Vec<_>>();
}
当链超过 ~4 个适配器,用命名的中间变量分解或提取 helper:
#![allow(unused)]
fn main() {
let env_tags = data.iter()
.filter_map(|x| x.metadata.as_ref())
.flat_map(|m| m.tags.iter());
let allowed: Vec<_> = env_tags
.filter_map(|t| t.strip_prefix("env:"))
.filter(|env| allowed_envs.contains(env))
.map(|env| env.to_uppercase())
.sorted()
.collect();
}
函数式不足:Rust 有词汇的 C 风格循环
#![allow(unused)]
fn main() {
// 这只是 .any()
let mut found = false;
for item in &list {
if item.is_expired() {
found = true;
break;
}
}
// 写这个代替
let found = list.iter().any(|item| item.is_expired());
}
#![allow(unused)]
fn main() {
// 这只是 .find()
let mut target = None;
for server in &fleet {
if server.id == target_id {
target = Some(server);
break;
}
}
// 写这个代替
let target = fleet.iter().find(|s| s.id == target_id);
}
#![allow(unused)]
fn main() {
// 这只是 .all()
let mut all_healthy = true;
for server in &fleet {
if !server.is_healthy() {
all_healthy = false;
break;
}
}
// 写这个代替
let all_healthy = fleet.iter().all(|s| s.is_healthy());
}
标准库有这些是有原因的。学习词汇,模式变得明显。
关键要点
- Option 和 Result 是单元素集合。 它们的组合器(
.map()、.and_then()、.unwrap_or_else()、.filter()、.zip())替换大多数if let/match样板。- 使用
bool::then_some()—— 它在每种情况下替换if cond { Some(x) } else { None }。- 迭代器链赢在数据管道 —— filter/map/collect 带零可变状态。它们编译到与循环相同的机器码。
- 循环赢在多输出状态机 —— 当你构建多个集合、在分支中做 I/O、或管理状态转换。
?操作符是两全其美 —— 函数式错误传播带命令式可读性。- 在 ~4 个适配器处分解链 —— 用命名的中间物提高可读性。过度函数式化与函数式不足一样糟。
- 学习标准库词汇 ——
.any()、.all()、.find()、.position()、.sum()、.min_by_key()—— 每个都用单个揭示意图的调用替换多行循环。
另见: 第 7 章 了解闭包机制和
Fntrait 层次结构。第 10 章 了解错误组合器模式。第 15 章 了解流畅 API 设计。
练习:重构命令式到函数式 ★★(约 30 分钟)
将以下函数从命令式重构为函数式风格。然后识别函数式版本更糟的一个地方并解释为什么。
#![allow(unused)]
fn main() {
fn summarize_fleet(fleet: &[Server]) -> FleetSummary {
let mut healthy = Vec::new();
let mut degraded = Vec::new();
let mut failed = Vec::new();
let mut total_power = 0.0;
let mut max_temp = f64::NEG_INFINITY;
for server in fleet {
match server.health_status() {
Health::Healthy => healthy.push(server.id.clone()),
Health::Degraded(reason) => degraded.push((server.id.clone(), reason)),
Health::Failed(err) => failed.push((server.id.clone(), err)),
}
total_power += server.power_draw();
if server.max_temperature() > max_temp {
max_temp = server.max_temperature();
}
}
FleetSummary {
healthy,
degraded,
failed,
avg_power: total_power / fleet.len() as f64,
max_temp,
}
}
}
🔑 解答
total_power 和 max_temp 是干净的函数式重写:
#![allow(unused)]
fn main() {
fn summarize_fleet(fleet: &[Server]) -> FleetSummary {
let avg_power: f64 = fleet.iter().map(|s| s.power_draw()).sum::<f64>()
/ fleet.len() as f64;
let max_temp = fleet.iter()
.map(|s| s.max_temperature())
.fold(f64::NEG_INFINITY, f64::max);
// 但三路分区 BETTER 作为循环。
// 函数式版本将需要三个独立的 pass
// 或带三个可变累加器的 awkward fold。
let mut healthy = Vec::new();
let mut degraded = Vec::new();
let mut failed = Vec::new();
for server in fleet {
match server.health_status() {
Health::Healthy => healthy.push(server.id.clone()),
Health::Degraded(reason) => degraded.push((server.id.clone(), reason)),
Health::Failed(err) => failed.push((server.id.clone(), err)),
}
}
FleetSummary { healthy, degraded, failed, avg_power, max_temp }
}
}
为什么循环对三路分区更好: 函数式版本将需要三个 .filter().collect() pass(3x 迭代),或带 tuple 内三个 mut Vec 累加器的 .fold() —— 这只是用更糟语法重写的循环。命令式单 pass 循环更清晰、更高效、更易扩展。