custom implementations
Opened this issue · 5 comments
Observation: The main pain point of enums is the repetitiveness of match-statements. Which are scattered around at different locations. This is good for adding and removing a new trait implementation (which is a local change of code). But it is not good for adding and removing enum-variants. Quick-error does code transformations to invert this for a specific set of traits.
Here is the (possibly crazy) idea: What about a code transformation that is generic in terms of the traits, too - like this example
IN
enum_transform!(
pub enum Foo {
X => {
#[define] Bar::baz(/* optional arguments*/) -> { Ok(()) }
},
Y => {
#[define] Bar::baz(/* optional arguments*/) -> { Err(()) }
}
}
impl Bar for Foo {
fn baz(&self, /* .. */) -> Result<(), ()> {
// ...
match *self
#[insert] { Bar::baz(/* optional arguments*/) }
// ...
}
}
)
OUT
pub enum Foo {
X,
Y
}
impl Bar for Foo {
fn baz(&self, /* .. */) -> Result<(), ()>
// ...
match *self {
X => { Ok(()) };
Y => { Err(()) };
}
// ...
}
}
If we had such a generic enum_transform macro, then
(a) quick_error could be rewritten in terms of enum_transform AND
(b) quick_error could itself could allow generic substitutions like this
EDIT:
- Minor corrections
I think I will start this as a completely separate library - if it works out, we can see if it makes sense.
Yes. That's my observation too. But I don't have a time to do it myself. So yes, please start it as a separate library. It's good idea besides the error handling.
Ok, I already started this. Now I am stuck at the generalized problem of FIND_IMPL (instead of FIND_DESCRIPTION_IMPL, etc.). So in a simplified version, I want to do a search via macros
macro_rules! enum_transform{
(FIND $needle:ident
{$pop:ident $( $hay:ident )*}
) => {
// PSEUDO-CODE >>
if($pop == $needle) {
true
} else {
enum_transform!{FIND $needle {$( $hay )*}
}
// << PSEUDO-CODE
}
(FIND $needle:ident {}
) => {
false
}
}
assert_eq!(enum_transform!(FIND bar {foo bar baz}), true);
This can partly be reduced to this problem:
assert!(equal!(foo foo));
assert!(!equal!(foo bar));
Which rust macro is capable to do this check on the AST-level?
Of course I can do something like
macro_rules! equal{
($a:ident $b:ident)
=> { stringify!($a) == stringify!($b) }
}
Rust should be able to reduce this to true booleans at compile time - but strictly after the macros have completed. I believe this would not solve all problems, because the compiler would probably complain about invalid expressions, even if they are unreachable like this
if equal!(foo bar) {
// .. invalid expressions - rust would still complain, although they are unreachable
}
Even if it worked, I would not like this workaround very much.
Hm. This could be a show stopper. What do you think?
I was more or less confident until I realised this problem.
Hm. I should try if macros can dynamically define new local macros and call them. I dont know if Rust allows that, if so, it would be very powerful.
Ok, after playing around with this:
This works in general - which is astonishing, given the limitations of macro_rules. But there are two very big drawbacks doing this whole thing with macro_rules!
- The recursion depth of my prototype scales linear with the number of tokens O(enum) + O(impl). Due to some macro limitations, I would even need to make two passes over the implementations (first to replace the match-statements with higher-order-macros, this must be done in a macro-buffer before the implementations are written out, then another pass to replace
self
withself
due to macro hygiene, this must be done after the impl-definition has started and the newself
is in scope). Currently I try it in one pass, where only one of these succeeds and the other one fails (which one depends on whether I do this pass 'early' or 'late'). I am confident, that both work, if I split it into two passes. But two passes means at least2 * len(impl)
recursions. Even the most trivial cases would then be around the default recursion limit of 64. It is possible and very easy to increase this limit, but it does not seem like a very good idea. Even with one pass it would be problematic. - Last but not least, it is not possible to use macros inside macro-invocations (at least not with some additional trickery).
Takeaway: I think this should better be done with a procedural macro - but there is currently no way to write procedural rust macros for the stable toolchain. So I think this must wait until the macro reform.
Btw - This is how far I got: https://github.com/colin-kiegel/enum-transform/blob/master/src/macros.rs. I think this is a dead end for above reasons - but at least I learned a lot about rust macro_rules!. :-)