结论:库的错误类型应该是具体的、结构化的枚举——每种失败一个变体,实现 std::error::Error(含 Displaysource()),用 thiserror 减少样板。这样调用方能 match 精确处理个别错误,也能沿着 source 链定位根因。和应用层用 anyhow 的"不透明大错误"正好相反。

展开:设计原则:1)公开枚举、按失败模式分变体(IoParseTimeout),变体携带上下文(路径、行号),不要把错误压成字符串——字符串无法 match。2)用 #[non_exhaustive] 给枚举留扩展余地,避免加变体成为破坏性变更;同时意味着调用方必须写 _ 兜底。3)#[from] 自动转换底层错误要谨慎——它会把依赖类型暴露进公共 API,升级依赖可能变成破坏性变更,必要时改为不透明变体存 Box<dyn Error + Send + Sync>。4)库内部不要用 unwrap,panic 剥夺了调用方的错误处理权。

#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum ConfigError {
    #[error("读取配置文件失败: {path}")]
    Io { path: String, #[source] source: std::io::Error },
    #[error("第 {line} 行解析失败")]
    Parse { line: usize },
}

易错点:Error::source 链要接上,否则排障时丢失底层原因。追问方向:错误类型与日志的分工、Result<T> 默认参数 Infallible 的用途。