-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider how to support tuple variants #61
Comments
Given that you have to annotate the source and backtrace in the tuple struct, i feel like you're not gaining very much (assuming you want to use a tuple struct to save some keystrokes). And as shown in your example – it could be very confusing in the order dependent usage with context selectors. I don't feel like this is an overall win to the library. I think its only purpose here is to ease the transition from |
I'm using failure but I want to migrate to snafu and I find tuples very useful then I want to handle the error with a In the following example assume that use snafu::ResultExt;
use snafu::Snafu;
#[derive(Debug, Snafu)]
pub enum ErrorA {
#[snafu(display("ErrorA1"))]
ErrorA1,
#[snafu(display("ErrorA::B: {}", source))]
B { source: ErrorB },
}
#[derive(Debug, Snafu)]
pub enum ErrorB {
#[snafu(display("ErrorB1"))]
ErrorB1,
#[snafu(display("ErrorB::C: {}", source))]
C { source: ErrorC },
}
#[derive(Debug, Snafu)]
pub enum ErrorC {
#[snafu(display("ErrorC1"))]
ErrorC1,
}
fn run() -> Result<(), ErrorA> {
Err(ErrorC::ErrorC1).context(C).context(B)?
}
fn main() {
match run() {
Ok(()) => {
// all fine
}
Err(ErrorA::B {
source: ErrorB::C {
source: ErrorC::ErrorC1,
},
}) => {
// do something special
}
Err(e) => {
// display error and exit
println!("{}", e);
std::process::exit(1);
}
}
} With failure the same match will be written as: match run() {
Ok(()) => {
// all fine
}
Err(ErrorA::B(ErrorB::C(ErrorC::ErrorC1))) => {
// do something special
}
Err(e) => {
// display error and exit
println!("{}", e);
std::process::exit(1);
}
} IMO with tuples you can write cleaner code in this case. |
@oblique thanks for your insights! I don't really see how this is "cleaner" (whatever this means). You write less, yes – but i don't think that's all there is to "cleaner code". I see your usecase and the demand to write less but i don't think it outweighs the problems that this would introduce e.g with context selectors. |
That was the first issue I've stumbled in before I even try to use snafu, and here's my first thoughts. I have not found any explicit notice that tuple-structs are unsupported in user's guide. Before it is implemented (or decided to not) I think it's better be documented with link to that issue. Some existing external types might be a tuple struct. E.g. std::mpsc::SendError, thus for fn send_error_inner<T>(err: &mpsc::SendError<T>) -> &T {
&err.0
}
#[derive(snafu::Snafu)]
pub enum Error {
#[snafu(display("err: {}", send_error_inner(&source)))]
SomeError { source: mpsc::SendError<Command> },
...
} The other thing - tuple-struct support might be a helpful for migration. So rewriting existing error types might need less to change for getting a somewhat result. Thus in my opinion it is better be added, but with a notice of "undesired usage". For example that might be just a line in guide, or even a warning (hopefully with suppressing). As an implementation, I prefer accessing fields in failure-like style (e.g. _1, _2, ...). Introducing a special keyword seems obscure and might conflict with other identifiers. |
There are plenty of things that any given library doesn't support; listing all of them seems counterproductive. Where would you suggest putting this information in the guide?
Accessing a tuple inside of the enum variant works fine: #[derive(Debug, snafu::Snafu)]
pub enum Error {
#[snafu(display("err: {:?}", source.0))]
SomeError { source: std::sync::mpsc::SendError<std::process::Command> },
}
Perhaps, which is a reason that this has remained open, but is blocked on figuring out enough pieces to be useful without making the rest of the library worse.
Using underscores is already a conflict: const _2: i32 = 42;
#[derive(Debug, snafu::Snafu)]
pub enum Error {
#[snafu(display("example: {} {}", _1, _2))]
SomeError { _1: i32 },
} |
You could use dot instead of underscore, the same way thiserror does. |
That's interesting! Of course it's written by dtolnay, which explains all the fancy custom parsing going on there. That's the big problem of such a solution: fn main() {
println!("{}", .0);
}
That means that thiserror has some code that attempts to parse the format expression arguments. If it finds anything that matches, it ... does something with it. I'm not yet sure what it's rewritten to. There's probably a hidden variable (such as The big downsides I see of such a path is that all of that parsing has to be custom, which seems complicated seeing as how any valid expression can be put in the format. There's also a worry in the back of my head that the Rust compiler will decide to accept |
My current thinking is to make tuple variants effectively equivalent to this: #[derive(Debug, Snafu)]
enum Error {
#[snafu(context(false))]
OneWay { source: AnError },
SameThing(AnError),
} That is, a tuple variant can have exactly 1 element which must be a source error. It functions the same as a context-less variant and would be available to the |
People ask about tuple variants:
There are a few reasons I've avoided adding it this far.
Identifying source errors / backtraces
Right now, we tell what the underlying error is by using the name
source
(mirroringstd::Error::source
). #10 proposes extending this, so using the attribute from there, you'd be required to annotate the appropriate field:Context selectors would also be order-dependent
Since context selectors mirror the corresponding variant, they would become tuple structs. The automatically-handled fields would be removed, which means that the parameters of the selector are different:
Accessing the fields in
Display
has no standard syntaxRight now, we bind the fields in the variant to the given names, but tuples don't have valid names to use. We'd have to make up some syntax (like failure) or create some variable:
None of the problems are insurmountable, but is this a pattern that we want to even encourage? What are the benefits of supporting tuple variants?
The text was updated successfully, but these errors were encountered: