-
Notifications
You must be signed in to change notification settings - Fork 60
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
Snafu for enums with variant types #199
Comments
Supporting use winapi::shared::minwindef::DWORD;
#[derive(Debug, Snafu)]
#[snafu(display("Function {} failed with code {}", func_name, code))]
pub struct WindowsError {
code: DWORD,
func_name: &'static str,
} In these use cases, no outer enum type is ever actually needed, because all of the different errors that may occur are represented by a single struct. |
@migi thank you for the clear and well-thought-out case. I find myself basically in complete agreement. I think the only issues would be those that might come up during implementation of such functionality. I doubt I'll be able to get around to implementing this any time in the near future, but if anyone is interested in tackling it, feel free to ask for some directions or feedback and I'll see what I can do! |
A recent comment on Discord seems like it would be helped by this:
Instead of enum Error {
Example { source: ApiError },
} it could be enum Error {
Example(ExampleOpaque),
}
struct ExampleOpaque { source: ApiError } |
Looks like it. I had started the implementation of this (in PR #199) but looks like that PR slipped under the radar. |
One thing I've talked about with people has been how to support "absorbing" subsets of errors. In code: struct ErrorA;
struct ErrorB;
struct ErrorC;
enum ErrorAb { A(ErrorA), B(ErrorB) }
fn ab() -> Result<(), ErrorAb> { todo!() }
enum ErrorAbc { A(ErrorA), B(ErrorB), C(ErrorC) }
fn abc() -> Result<(), ErrorAbc> { todo!() } I'd like to think about if we should make it easy to "absorb" from impl From<ErrorAb> for ErrorAbc {
fn from(other: ErrorAb) -> Self {
match other {
ErrorAb::A(a) => ErrorAbc::A(a),
ErrorAb::B(b) => ErrorAbc::B(b),
}
}
} I think that syntactically this would be annoying because there's no way for the macro to know which variants exist on the type being absorbed, so it'd have to look like: #[derive(Snafu)]
#[snafu(absorb(ErrorAb, A, B))]
enum ErrorAbc { A(ErrorA), B(ErrorB), C(ErrorC) } Which is fairly brittle as new variants are added. |
Motivation
Currently, Snafu is designed to work with enums like
However, a somewhat common pattern in Rust (examples 1, 2) is to have enums like
While it is slightly more verbose, it has a few advantages:
Since enum variants are not types, it's not possible to accept a
MyEnum::VariantA
as a function parameter or to store it somewhere. You can only pass/storeMyEnum
. However, when you've made each variant its own type,VariantA
can be passed around or stored.Note: there is an RFC which proposes to add variant types to Rust, which would remove this advantage. However, this comment doesn't give the impression that this will be implemented soon.
If multiple enums can have the same variant, you must repeat that variant (and all its fields) in each enum definition. With the second style, you just repeat the
VariantA(VariantA)
line.When matching on this kind of enum, if a variant has a lot of fields, in the first style you have to match like
whereas if you use the second style, you can match like (example)
Snafu does not support this second style of error enum, but I think support for it can be added backwards-compatibly, since
#[derive(Snafu)]
currently isn't implemented for regular structs (it's only implemented for single-field tuples), and it also isn't implemented for enums with single-field tuple variants (only struct-like and unit variants). So this second style of enums neatly falls outside of what Snafu currently supports.The only issue is that there will be a naming collision between variant data structs and context selectors. Both are (according to the regular conventions) given the same name as the enum variant. So some bikeshedding will need to be done here, which I do below.
Bikeshedding
My vote for how to color the bike shed is as follows:
Allow
#[derive(Snafu)]
for regular Rust structs. If the struct is calledFoo
, this creates a new structFooContext
, which is the context selector. Any attributes that currently work on struct-like enum variants work on structs as well, and they do the same thing, essentially as if you had an enum with a single variant.Display
andError
are implemented forFoo
,IntoError<Foo>
is implemented forFooContext
, etc.Allow
#[derive(Snafu)]
for enums that have single-field tuple variants.Display
andError
are implemented as usual, but for these single-field tuple variants, no context selector is generated (it should've been generated in step 1 above). For an enumMyEnum
with variantVariantA
, I thinkIntoError<MyEnum>
should not be implemented forVariantAContext
even though it could, but instead we should implementFrom<VariantA>
forMyEnum
. I think this should allow.context(VariantAContext { ... })?
to work in a function returningMyEnum
(the?
operator does the.into()
). Also implementingIntoError<MyEnum>
forVariantAContext
would probably break type inference.I suppose there should also be a way to specify the name of the generated context selector. This would be part of the
#[snafu(...)]
attribute, e.g.#[snafu(context(MySelectorStructName), display(...)]
or something. This would need to be specified both on the variant-data struct and on the enum variant.Here's how the example from the snafu docs homepage would look with this style:
The text was updated successfully, but these errors were encountered: