In Rust, context belongs on the line that actually fails
The question mark operator rethrows an error without saying which step failed. Structured errors in the core, Context in the application layer, with the input attached, is what makes a log line locatable.
? is so convenient that it is easy to forget it does nothing at all: it rethrows the Err upwards. The call stack is discarded on the way, and what reaches your log may be No such file or directory. Which file? Who was reading it?
Two layers
My rule is to split errors into two layers:
| Layer | Error type | Audience |
|---|---|---|
| Library and core logic | enum (via thiserror) |
Callers who need to match and branch |
| Application and service | anyhow::Error plus Context |
Humans reading logs |
Core logic uses an enum because callers may genuinely need to tell cases apart: retry, degrade, return a different status code. Once you are inside handling one request, nobody matches any more; what is needed is a record that can be located.
Attach it where the failure happens
Context belongs on the line that can actually fail, not at the function entrance:
use anyhow::{Context, Result};
pub fn load_config(path: &Path) -> Result<Config> {
let text = fs::read_to_string(path)
.with_context(|| format!("读取配置 {}", path.display()))?;
let config: Config = toml::from_str(&text)
.with_context(|| format!("解析配置 {}", path.display()))?;
Ok(config)
}
The two with_context calls separate a file that cannot be read from a file that was read but contains something invalid. Drop either one and the log only ever tells you about one of them.
Do not downgrade errors to String
map_err(|e| e.to_string()) looks like simplification and actually throws away the ability to classify: the caller receives text and can only print it. It also breaks the chain, so source() can no longer reach the underlying cause. anyhow keeps the chain, because Context wraps rather than replaces.
Put the input in the message
The test for a good error message is plain: can someone without a debugger read this one line and know what to check next? failed to parse config fails it. 解析配置 /etc/app/conf.toml: 第 12 行缺少 = passes. Paths, line numbers, key names, upstream status codes are information you have in hand and the reader does not.
Every word saved in an error message becomes a reproduction later.

Comments
…