第四章 错误处理与 Result/Option

核心能力标签:错误类型设计 · 传播机制 · 自定义错误 · panic 控制

知识结构:错误处理四个层次

层次关键概念面试高频考点
基础层Option / Result 枚举unwrap/expect 滥用风险
传播层? 运算符、From trait错误类型转换链
自定义层thiserror / anyhow / Display + Error错误原因链 (source)
控制层panic! / catch_unwind / 钩子panic 安全与终止策略
错误处理回答路径 区分可恢复/不可恢复 选用 Result / Option 考虑 ? 传播还是 map 自定义错误类型 实现 Display+Error 状态码与日志策略 panic 边界:只在不可恢复时使用,利用钩子做资源清理

面试问答卡(3组)

🟢 Q1:Rust 中如何区分使用 Result 和 panic?

考察点错误哲学理解、panic 边界判断、代码健壮性
回答框架① 分类:可恢复错误用 Result,不可恢复用 panic;② 典型 panic 场景:数组越界、除零、逻辑断言;③ 业务错误一律用 Result;④ 库代码避免 panic,留错误处理给调用方。
示例回答“Rust 的核心原则是:可恢复错误用 Result 返回,让调用方决定;不可恢复错误用 panic!终止。例如文件不存在用 Result,下标越界则 panic。我编写库时,所有预期错误都返回 Result,仅在内部逻辑 bug 时 panic。” (约85字)
可能追问如何自定义 panic 行为?catch_unwind 是否推荐?
避坑提醒❌ 不要用 panic 代替普通错误处理;❌ 不要在库中随意 unwrap;✅ 生产代码中 panic 前应记录关键上下文。

🟢 Q2:? 运算符如何实现错误类型自动转换?

考察点From trait 理解、错误链设计、标准库机制
回答框架① ? 调用 From::from 将错误类型转为函数返回的错误类型;② 需要为自定义错误实现 From<其他错误>;③ 常见模式:使用 thiserror 或 anyhow 简化;④ 错误链的 source 方法提供根因。
示例回答“? 会调用 Into::into 自动转换错误类型。例如函数返回 MyError,内部使用 std::io::Error,只需 impl From<io::Error> for MyError,? 就能自动转换。我用 thiserror 宏快速派生,避免手写样板代码。” (约90字)
可能追问多个错误类型如何统一?anyhow 和 thiserror 各自适用场景?
避坑提醒❌ 忘记为自定义错误实现 From 导致编译失败;✅ 二进制程序多用 anyhow,库多用 thiserror;✅ 错误类型应实现 std::error::Error。

🟢 Q3:如何设计一个生产可用的自定义错误类型?

考察点枚举设计、Display/Source 实现、thiserror 使用
回答框架① 使用枚举变体区分错误类别;② 每个变体可携带额外数据(如错误码、上下文);③ 派生 thiserror 快速实现 Display 和 Error;④ 提供简洁的 From 实现;⑤ 考虑序列化(如 serde)用于 API 返回。
示例回答“我会用 enum 定义:`#[derive(thiserror::Error)] pub enum MyError { #[error(\"IO error: {0}\")] Io(#[from] io::Error), #[error(\"Not found: {id}\")] NotFound { id: u64 } }`。每个变体包含针对性数据,自动实现 Error trait,便于上游匹配处理。” (约100字)
可能追问如何支持错误链?如何给错误添加 HTTP 状态码映射?
避坑提醒❌ 不要把所有错误包装成一个字符串类型;❌ 避免过度设计导致变体过多;✅ 为每个变体提供 clear 的文档注释。

项目案例:分布式配置中心错误处理设计

项目名称ZebraConfig — 高可用配置中心 agent
业务背景微服务集群需要实时拉取配置,agent 负责监听变更并更新本地文件,要求错误处理覆盖网络、文件、序列化、校验等场景
规模指标1000+ 节点,每秒 2000+ 配置变更事件,QPS 峰值 5000+
技术栈Rust + tokio + thiserror + anyhow + serde + tracing
核心难点多种错误类型统一管理;支持重试与降级;panic 隔离不崩溃主循环;错误日志可追踪
面试高频追问如何避免单个文件错误导致整个 agent 挂掉?如何处理配置文件损坏后的回滚?panic 在异步任务中如何捕获?
可迁移回答话术“我们使用统一错误枚举,每个操作返回 Result<T, AgentError>,核心循环对每个任务 spawn 并 catch_unwind 捕获 panic,记录结构化日志后继续处理下一个任务。文件损坏时先备份再尝试旧版本恢复,失败则主动上报 metrics 并进入降级模式。” (约100字)
💡 面试加分点:
• 提到 #[track_caller] 定位 panic 源
• 提及使用 eyre 或 color-eyre 增强错误报告
• 主动说明通过 abort / unwind 配置控制 panic 策略
⚠️ 高频踩坑区:
• unwrap / expect 在异步上下文可能造成 task 泄露
• 过度使用 catch_unwind 会掩盖逻辑错误
• 不要忽略 ? 运算符的隐式转换带来的性能开销(极小,但可讨论)