Rust 错误类型设计:库错误要能被上层恢复

发布时间:2026/7/5 0:48:13
Rust 错误类型设计:库错误要能被上层恢复 Rust 错误类型设计库错误要能被上层恢复一、错误不是只为了打印Rust 的Result很适合表达失败但错误类型设计不好上层仍然很难处理。很多库直接返回字符串错误调用方只能打印无法判断是重试、提示用户还是终止程序。错误类型的价值是把失败语义交给上层。库代码应该提供可匹配的错误种类并保留必要上下文。应用层再决定日志、重试和用户提示。二、错误要分层flowchart TD A[IO 错误] -- D[库错误] B[解析错误] -- D C[业务校验错误] -- D D -- E[应用处理]底层错误包括 IO、网络、解析、权限等。库层可以把它们包装成自己的错误类型但不要丢失来源。应用层需要知道失败大类。例如配置文件不存在和配置格式错误处理方式不同。前者可以提示创建配置后者要提示修正格式。字符串错误无法稳定区分。三、枚举适合表达可恢复错误#[derive(Debug)] pub enum ConfigError { NotFound, InvalidFormat(String), Io(std::io::Error), } pub fn load_config(path: str) - ResultString, ConfigError { std::fs::read_to_string(path).map_err(ConfigError::Io) }真实项目可以使用thiserror简化实现。关键是错误类型要让调用方能够 match而不是只能看字符串。match load_config(app.toml) { Ok(cfg) println!({cfg}), Err(ConfigError::NotFound) eprintln!(请先创建配置文件), Err(err) eprintln!(配置加载失败: {err:?}), }应用层决定如何展示错误。库层不要直接打印也不要直接退出进程。四、上下文要适量保留错误需要上下文但不能把敏感信息塞进去。路径可以保留相对路径Token 不应进入错误信息。AI 工具里尤其要注意模型请求错误可能包含用户输入摘要。还要区分可恢复和不可恢复。配置缺失、网络超时、模型限流都可能恢复内部状态损坏、协议不兼容可能需要终止。错误类型应体现这种差异。错误链也要保留。上层看到ConfigError::Io时最好仍能访问底层std::io::Error。这样日志里可以包含系统错误码用户提示则保持简洁。面向人和面向排障的错误信息不必完全一样。anyhow和thiserror适用场景也不同。应用入口可以用anyhow快速附加上下文库接口更适合暴露明确 enum。库代码如果返回anyhow::Error调用方就很难细分处理。还要为错误写测试。触发配置缺失、格式错误、权限不足确认返回的 variant 正确。错误路径不测试后续重构很容易把可恢复错误变成普通字符串。最后错误文案要稳定。CLI 用户和脚本可能依赖错误码或退出码不要随意改变语义。退出码也应分层。配置错误、用户输入错误、外部服务错误、内部错误可以对应不同退出码区间。这样脚本能做自动处理而不是只能判断成功或失败。错误上下文要用source串起来。比如读取配置失败底层可能是权限不足或文件不存在。上层错误说明“配置加载失败”底层 source 保留系统原因。两层信息都重要。还要避免过度包装。每一层都加一段冗长上下文最后错误信息会像套娃。只在跨越抽象边界时补充有价值的信息即可。最后错误处理规范应写进贡献文档。多人协作时错误风格不统一会让 CLI 体验割裂。还要警惕错误信息里泄漏敏感数据。有一次日志里打印了完整的 API 响应体其中包含了用户的手机号和请求原文。后来在错误类型里加了脱敏方法自动把已知的敏感字段替换为[REDACTED]。错误信息既要帮助排查也要守住隐私底线。五、总结Rust 错误类型设计要分层表达失败语义让上层能 match、恢复、重试或提示用户。库代码不要直接打印和退出。错误不是最后一行日志。好的错误类型是系统把失败处理权交给调用方的接口。