snafu icon indicating copy to clipboard operation
snafu copied to clipboard

Allow unboxing `source`

Open Kyuuhachi opened this issue 6 months ago • 5 comments

This program fails to downcast its source errors, since they are Box<MyError>, not MyError. As briefly mentioned on discord.

use snafu::ResultExt as _;

#[derive(Debug, snafu::Snafu)]
pub enum MyError {
	#[snafu(display("error: {error_code:08X}"))]
	LeafError { error_code: u32 },
	#[snafu(display("at depth {depth}"))]
	NodeError {
		depth: u32,
		#[snafu(source(from(MyError, Box::new)))]
		source: Box<MyError>
	},
}

fn main() {
	let e = fake_fn(3).unwrap_err();

	for e in std::iter::successors(Some(&e as &dyn std::error::Error), |e| e.source()) {
		if let Some(e) = e.downcast_ref::<MyError>() {
			println!("downcasted: {}", e);
		} else {
			println!("failed: {}", e);
		}
	}
}

fn fake_fn(depth: u32) -> Result<(), MyError> {
	if depth == 0 {
		LeafSnafu { error_code: 0xDEADBEEFu32 }.fail()
	} else {
		fake_fn(depth - 1).context(NodeSnafu { depth })
	}
}

Kyuuhachi avatar Jul 02 '25 16:07 Kyuuhachi

How does this syntax feel?

#[derive(Debug, Snafu)]
enum RecursiveError {
    Leaf,

    Node {
        #[snafu(source(from(RecursiveError, Box::new)))]
        #[snafu(source(via(|e| &**e)))]
        source: Box<RecursiveError>,
    },
}

I don't love via as the keyword; happy to entertain alternatives.

shepmaster avatar Jul 23 '25 14:07 shepmaster

Oh, it's time to shed the bike? Some other alternatives might be into and as. into is nice as a counterpart to from, but usually into is for value-to-value while as is for ref-to-ref. But as is a keyword, so that doesn't feel great either, even if macros aren't bound by rust keywords.

Somewhat relatedly, a source(boxed) attribute might be nice, as a shorthand for source(from(T, Box::new), blah(|e| &**e)).

Kyuuhachi avatar Jul 23 '25 14:07 Kyuuhachi

But as is a keyword, so that doesn't feel great either, even if macros aren't bound by rust keywords.

I don't mind the as, especially as it's going to be namespaced by the source(). I was going to say that we already use mod, but I guess that's module. I think I originally reached for as as well, then picked via as a quick synonym that would be easy to grep for to change it 😉

source(boxed) attribute might be nice

It'd have to be boxed(T), and it's annoying that you can't access ::alloc::boxed::Box in a non-no_std program like you can access ::core::....

shepmaster avatar Jul 23 '25 15:07 shepmaster

It'd have to be boxed(T)

My thought was that it'd scrape out the inner type from the path, but that's pretty unreliable especially with type aliases. So yeah, boxed(T) is better.

and it's annoying that you can't access ::alloc::boxed::Box

But strictly speaking you don't need to. Could make it work with any From<T> + Deref<Target=T>.

Kyuuhachi avatar Jul 23 '25 15:07 Kyuuhachi

I think in french via is exactly right :p

If I get it it's just a inline map no ?

Stargateur avatar Jul 24 '25 01:07 Stargateur