DEV Community

amaendeepm
amaendeepm

Posted on

I Fell for `?`. Then I Couldn't Debug Anything.

The ? operator in Rust is genuinely beautiful. You go from a mess of match arms to something that reads like a sentence:

async fn process_report(payload: &[u8]) -> Result<(), AppError> {
    let envelope = parse_envelope(payload)?;
    let member   = resolve_member(&envelope.member_id)?;
    let positions = extract_positions(&envelope)?;
    persist_positions(&member, positions).await?;
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Clean. Compositional. I used it everywhere.

Then one day, records stopped being written to the database. No panic. No error log. No trace of anything going wrong.

The problem was obvious in hindsight. Any one of those four ?s could have triggered an early return. The error was propagating up to a caller that wasn't logging it properly. I had no idea which step was the culprit without manually re-running the pipeline with test fixtures.

The ? operator had silently swallowed my debugging surface.

The fix that worked:

let envelope = parse_envelope(payload)
    .map_err(|e| { tracing::error!(step = "parse_envelope", error = ?e, "failed"); e })?;

let member = resolve_member(&envelope.member_id)
    .map_err(|e| { tracing::error!(step = "resolve_member", error = ?e, "failed"); e })?;
Enter fullscreen mode Exit fullscreen mode

Every potential exit point now announces itself. The error still propagates, the caller still gets it, but the point of propagation is now observable.

The lesson: ? is ergonomics for writing code. Logs are ergonomics for operating it. In any pipeline where a silent early return would cause missing data or unnoticed failures, wrap every ? with a log. The verbosity is worth it.

Top comments (0)