惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
Malwarebytes
Malwarebytes
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cybersecurity and Infrastructure Security Agency CISA
F
Future of Privacy Forum
C
Cisco Blogs
T
The Exploit Database - CXSecurity.com
A
Arctic Wolf
S
Securelist
K
Kaspersky official blog
S
Schneier on Security
T
ThreatConnect
T
Tenable Blog
Spread Privacy
Spread Privacy
T
True Tiger Recordings
AWS News Blog
AWS News Blog
F
Fox-IT International blog
量子位
T
Threatpost
V
Vulnerabilities – Threatpost
C
CERT Recently Published Vulnerability Notes
Cisco Talos Blog
Cisco Talos Blog
GbyAI
GbyAI
宝玉的分享
宝玉的分享
腾讯CDC
G
Google Developers Blog
aimingoo的专栏
aimingoo的专栏
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
S
SegmentFault 最新的问题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
U
Unit 42
雷峰网
雷峰网
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
O
OpenAI News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
The Register - Security
The Register - Security
MyScale Blog
MyScale Blog
小众软件
小众软件
A
About on SuperTechFans
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
博客园 - 三生石上(FineUI控件)
美团技术团队
Google Online Security Blog
Google Online Security Blog
P
Proofpoint News Feed
MongoDB | Blog
MongoDB | Blog

Lobsters

Lambda on Lambda: Serverless Haskell on AWS Announcing feed-repeat v1.0 Scaling Akvorado BMP RIB with sharding EYG news: A host of CLI improvements, new guides and new effects The Eternal Sloptember JS Crossword C array types are weird; and related topics Flatpak will depend on systemd – OSnews Migrating from Go to Rust | corrode Rust Consulting Building Pi With Pi abyss * your_dotfiles_are_not_a_distro Vivado Licensing Options How my minimal, memory-safe Go rsync steers clear of vulnerabilities From AFSK to Goertzel the entropy layer of a wavelet codec, on its own 10,000 Lines Later: When a Tool Became a Compiler - Rob Durst - Gleam Gathering 2026 Debian SE Linux and PinTheft fht-compositor: A dynamic tiling Wayland compositor A Network Allow-List Won't Stop Exfiltration — André Graf Does bulk memmove speed up std::remove_if? (No.) What is Git made of? wake up! 16b 声明式部分更新 | Blog | Chrome for Developers Don't Roll Your Own ... Dianne Skoll's Web Site - Remind “Long-Term Support” doesn’t mean what you think The Architecture of Open Source Applications (Volume 1)Berkeley DB Pardon MIE? - ironPeak Blog seriot.ch It's time to talk about my writerdeck hershey Cuneiforth: A Forth for your Chifir z386: An Open-Source 80386 Built Around Original Microcode waylandcraft - Minecraft Mod On the <dl> HP QuickWeb, Singular And Pointless mvm - a fast virtual machine for Go That one time I used Go panics for flow control A new suite of modern tools coming for editing and publishing RFCs From the Tabletop… The Digital Antiquarian .NET (OK, C#) finally gets union types🎉: Exploring the .NET 11 preview - Part 2 Revised^7 Report on Scheme, Large: Procedural Fascicle Draft is now public The Soul of Maintaining a New Machine - Third Draft | Books in Progress
Scoped Error in Rust
kanru.info v · 2026-05-25 · via Lobsters

I’ve never been fully satisfied with any error handling crate in Rust. I’ve tried many and even developed a few helpers. Here are the key issues I found with each. Theses are issues my scoped-error crate tries to address.

The Inspiration

anyhow - good for a drop-in Error type that just works, but requires adding .with_context() everywhere. It’s verbose and repetitive. Error reporting requires knowing how anyhow::Error handles format strings. Error propagation lacks location information; the alternative is backtrace, which pulls in heavy std dependencies.

thiserror - good for defining custom Error types. The #[from] implementation encourages a single Error type that encompasses all possible sources. But the ergonomics stop there. Using these types is still tedious if you want per-module errors with good context. The improvement over manually rolling Error types seems small compared to the syn and compile-time overhead.

snafu - combines manual context attachment with anyhow and thiserror patterns in one crate. However, I feel like I’m encoding all my error branches into Snafu contexts. Those implementation details don’t need to be public, yet snafu tightly couples the Error type to them. Maybe I’m using it wrong.

exn - a refreshing approach to error handling. I actually started my crate based on the pattern from the blog post Stop Forwarding Errors, Start Designing Them. The minor issues with exn 0.3 are: (1) you still need to remember .or_raise(err) for each fallible operation, and it’s easy to miss for intra-module method calls; (2) the Exn wrapper itself is not a std Error, so interop with other error types requires adapters like exn-anyhow or exn-stderr.

While switching between these error crates, I kept noticing a gap: with anyhow-like crates, you attach context at each call site, but the method itself lacks it.

Example:

use anyhow::Result;

fn read_config() -> Result<String> {
  let raw = std::fs::read_to_string("config.toml")?;
  Ok(raw)
}

fn complex_method() -> Result<()> {
  let cfg = read_config().context("validate config file")?;
  parse(cfg).context("parse config file")?;
  Ok(())
}

It’s easy to ? away the context when all your methods return anyhow::Result.

With exn-like crates, you define error context for each method and attach it to all error branches. snafu works similarly.

Example:

use exn::{Result, ResultExt};
use thiserror::Error;

#[derive(Debug, Error)]
#[error("MyError: {0}")]
struct MyError(&'static str);

fn read_config() -> Result<String, MyError> {
  let err = || MyError("read config file");
  let raw = std::fs::read_to_string("config.toml").or_raise(err)?;
  Ok(raw)
}

fn complex_method() -> Result<(), MyError> {
  let err = || MyError("complex method");
  let cfg = read_config().or_raise(err)?;
  parse(cfg).or_raise(err)?;
  Ok(())
}

The common issue: it’s easy to ? away the context when all methods share the same Result type.

Enter Scoped Error

I really liked exn’s approach: define an error closure to force conversion to the module-scoped Error type. But the repeated .or_raise(err)? gets annoying fast.

I started creating wrappers to mediate conversion from source errors to module-scoped Errors. I soon realized this pattern solves several ergonomic issues with other approaches, and checks the boxes I cared about.

Example:

use scoped_error::{Error, expect_error};

fn read_config() -> Result<String, Error> {
  expect_error("read config file", || {
    let raw = std::fs::read_to_string("config.toml")?;
    Ok(raw)
  })
}

fn complex_method() -> Result<(), Error> {
  expect_error("failed to do complex thing", || {
    let cfg = read_config()?;
    parse(cfg)?;
    Ok(())
  })
}

The core idea is simple: attach context exactly once. Not at every call site, not at every failure point, just between caller and logic. I want per-module Error types without manual conversion at every step.

expect_error() has three responsibilities: prepare context for future errors, force inner errors into a boxed type to type-erase the inner error, and wrap the outer error with the inner as its source.

The result: a clean, readable declaration of fallible operations. A default Error type is provided, but any std Error implementing WithContext works too.

The core library is tiny. It’s small enough to vendor directly into your project1.

The inner boxed error type Frame takes its name from exn. It converts any error to Box<dyn Error> and captures file location via #[track_caller] for a lightweight stack trace.

With the built-in Error type or the ErrorExt::report() helper, error trees (yes, trees are supported) render like this:

Error: failed to do complex thing, at src/main.rs:12:19
|-- read config file, at src/main.rs:5:19
`-- No such file or directory (os error 2)

The scoped-error crate also packs a few extras: a macro_rules! macro for creating common errors that implement WithContext, and a Many error type for multi-cause errors.

The crate is on crates.io, source is on Codeberg, and docs cover the details. If you try it, I’d love to hear your use cases. File an issue or drop me a note. I’m using this in my own projects now and finally Rust error handling feels right.

1

The core of the library is really tiny.

// Copyright (C) 2026 Kan-Ru Chen <kanru@kanru.info>
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

use std::any::Any;
use std::borrow::Cow;
use std::error::Error as StdError;
use std::fmt::{Debug, Display};
use std::panic::Location;

/// A trait for error types that can carry context information.
pub trait WithContext: StdError + Any {
    /// Attach a context layer to this error.
    fn with_context(self, context: Frame) -> Self;

    /// Get the location where this error was created or where context
    /// was attached.
    fn location(&self) -> Option<&'static Location<'static>>;
}

/// A single layer of error context.
pub struct Frame {
    /// The underlying error that caused this context.
    pub source: Box<dyn StdError + Send + Sync + 'static>,
    /// The location where this context was attached.
    pub location: &'static Location<'static>,
}

impl<T> From<T> for Frame
where
    T: Into<Box<dyn StdError + Send + Sync + 'static>>,
{
    /// Creates a `Frame` from any error type, capturing the
    /// caller's location.
    #[track_caller]
    fn from(value: T) -> Self {
        let source = value.into();
        let location = Location::caller();
        Frame { source, location }
    }
}

/// Low-level function for adding context with a custom error constructor.
#[inline(always)]
pub fn expect_error_fn<F, T, E>(
  err: F,
  body: impl FnOnce() -> Result<T, Frame>,
) -> Result<T, E>
where
    F: FnOnce() -> E,
    E: WithContext,
{
    body().map_err(|context| err().with_context(context))
}

/// Add context to errors, returning a custom error type.
#[inline(always)]
pub fn expect_error<T, E>(
    msg: impl Into<Cow<'static, str>>,
    body: impl FnOnce() -> Result<T, Frame>,
) -> Result<T, E>
where
    E: From<(Cow<'static, str>, Frame)>,
{
    body().map_err(|context| (msg.into(), context).into())
}