Closed nerditation closed 9 months ago
most of my errors have a single type as source
This is a big point the point of SNAFU and a major reason it was created; note how the example in the README essentially mirrors yours.
so I attempt to rewrite it
The big missing thing for me here is why?
The closest case I can think of to yours is when I have an error with many detailed variants that I want to categorize into coarser groups. For this, I usually create a separate enum and then an inherent method.
A common example is when reporting an error via HTTP and you want to get a status code:
#[derive(Debug, Snafu)]
enum Error {
Thing1,
Thing2,
Thing3,
}
enum Status {
Http400,
Http500,
}
impl Error {
fn status(&self) -> Status {
match self {
Self::Thing1 | Self::Thing2 => Status::Http400,
Self::Thing3 => Status::Http500,
}
}
}
most of my errors have a single type as source
This is a big point the point of SNAFU and a major reason it was created; note how the example in the README essentially mirrors yours.
oh, great, I must have read the example in the README
looooooong time ago and forgot about it. it indeed is almost the same as my case.
so I attempt to rewrite it
The big missing thing for me here is why?
maybe it's just a personal tastes. in my case, because the git2::Error
type already conveys very rich information in it, all I really need is a way to tell me where things went wrong, Location
is very handy, but it alone is not enough for me. in fact, I can almost get away with Whatever
in most cases, almost. there's still cases I need to match against the error kind.
instead of repeat source: GitError
for each variant like this:
use git2::Error as GitError;
#[derive(Debug, Snafu)]
pub enum MyError {
BbAlreadyExists { source: GitError },
BaseIndexDirty { source: GitError },
// many other cases
WorktreeConflict { source: GitError },
}
I'm more fond of the struct style:
enum ErrorKind {
BbAlreadyExists,
BaseIndexDirty,
// many other cases
WorktreeConflict,
}
struct MyError {
kind: ErrorKind,
source: GitError,
}
my current workaround is to use a custom extension trait for Result
, something like this:
pub trait MyResultExt<T> {
fn kind_context<Kind>(self, kind: Kind) -> Result<T, Error>
where
Kind: snafu::IntoError<ErrorKind, Source = snafu::NoneError>;
}
impl<T> MyResultExt<T> for Result<T, GitError> {
fn kind_context<Kind>(self, kind: Kind) -> Result<T, Error>
where
Kind: snafu::IntoError<ErrorKind, Source = snafu::NoneError>,
{
self.context(Snafu {
kind: kind.into_error(snafu::NoneError),
})
}
}
at least it solves my problem at hand.
The closest case I can think of to yours is when I have an error with many detailed variants that I want to categorize into coarser groups. For this, I usually create a separate enum and then an inherent method.
A common example is when reporting an error via HTTP and you want to get a status code:
[...]
thanks for the tips. now that I think about it, I probably don't need all those error kind variants in the first place, many of them (which I don't plan to handle, just to report to user) can be put into coarse groups indeed.
I'm gonna close this for now, as it's not really a problem of snafu
per se, and I have figured out a workaround too.
an aside:
I played with the error-stack
style error handling a bit these days, and I do find it less burden on the brain to just focus on the current error context, and not having to manage the source chain (and backtrace etc). snafu has implicit backtrace capture and location and more, but a source
field still must exist somewhere to have a proper source chain. I would think a similar construct could also work in snafu (doesn't need to force type erasure in my opinion), but I don't have a clear idea for now, maybe someday.
I encountered problem when writing a program with multiple error types. I have read #199 #399 and #406 which are slightly related but not quite the same.
the tldr
basically, I want to make the context selectors usable for different error types, given one of the types is just (kind of) a transparent wrapper of the other (with an added
source
field), e.g.long text alert:
I started with string contexts for prototyping, something like
after I got a picture of all the error conditions I might need propogate, I started to rewrite with enumerated errors:
this is much better, but I find out most of my errors have a single type as source (
std::io::Error
in above example code,git2::Error
in the actual program), so I attempt to rewrite it like this:I'm not very pleased with this, so I looked into the expanded code of derive macro, and came up with:
it works, but I have to manually handle every context selectors, once I try to make it generic, I run into conherence violation (E0210):
I think I understand the cause of the error, but I wonder if I can get some help from the upstream
snafu
crate, for example, after reading https://github.com/shepmaster/snafu/issues/399#issuecomment-1685279828_ I think that approach might suit my use case, i.e.:NoneError + Selector -> ErrorKind
,std::io::Error + Selector -> MyError
, in my case, my error types are not generic, so the compile error mentioned in the linked comments should not be an issue.PS
after writing the above, I realized my use case might be better served by the
error-stack
crate, where the source chain/stack is a separate concern than the error types themselvs. in theerror-stack
model, error types don't contain the source chain explicitly, but only contains information about current context of the relevant error, while the source chain is implicitly managed by an external wrapper type (with type erasure). the thing is, I don't like they way how they conflate errors with reports. anyway, I might give that a try.