match multiple enum types in macro - rust

i have a macro thats used for matching a Box<dyn error::Error> with different enums. with the version im using now, i need to call it repeatedly in an if else chain for each error type i want to match against.
#[macro_export]
macro_rules! dyn_match {
($e:expr, $($pat:pat => $result:expr),*) => (
if let Some(e) = $e.downcast_ref() {
match e {
$(
$pat => {$result; true}
),*
}
} else {false}
);
}
...
//fn example(x: u32) -> Result<u32, Box<dyn error::Error>>;
match example(9) {
Ok(_) => Ok(()),
Err(e) => {
if dyn_match!(e,
ExampleError1::ThisError(2) => panic!("it was 2!"),
i => {}
) {Ok(())}
else if dyn_match!(e,
ExampleError2::ThatError(8) => panic!("it was 8!"),
ExampleError2::ThatError(9) => panic!("it was 9!"),
i => {}
) {Ok(())}
else {panic!("{}",e)}
}
}
...
my goal is to make it so i can pass multiple error types to it and it'll do an individual match for each type. the only issue is, to convert the type from Box<dyn error::Error> to its original error type i need to call downcast_ref() on it. this works fine when every error passed is of the same type, but when i attempt to call a separate match block for each supplied arm it throws the error cannot infer type for type parameter 'T' declared on the associated function 'downcast_ref'
#[macro_export]
macro_rules! new_dyn_match {
($e:expr, $($pat:pat => $result:expr),*) => (
{
$(
if let Some(e) = $e.downcast_ref() { //cannot infer type for type parameter `T` declared on the associated function `downcast_ref`
if let $pat = e {$result}
}
)*
}
);
}
...
//fn example(x: u32) -> Result<u32, Box<dyn error::Error>>;
match example(9) {
Ok(_) => Ok(()),
Err(e) => {
new_dyn_match!(e,
ExampleError1::ThisError(2) => panic!("it was 2!"),
ExampleError2::ThatError(8) => panic!("it was 8!"),
ExampleError2::ThatError(9) => panic!("it was 9!"),
i => {}
);
Ok(())
}
}
...
im new to macros but it seems like, upon expansion during compile, it should be able to see which type each match would be compared against and implicitly downcast to that type. im really not sure how to fix this. any ideas?

got it!
#[macro_export]
macro_rules! dynmatch {
($e:expr, $(type $ty:ty {$(arm $pat:pat => $result:expr),*, _ => $any:expr}),*, _ => $end:expr) => (
$(
if let Some(e) = $e.downcast_ref::<$ty>() {
match e {
$(
$pat => {$result}
)*
_ => $any
}
} else
)*
{$end}
);
}
...
let _i = match example(3) {
Ok(i) => i,
Err(e) => {
dynmatch!(e, //the dynamic error to be matched
type ExampleError1 { //an error group
arm ExampleError1::ThisError(2) => panic!("it was 2!"), //arm [pattern] => {code}
_ => panic!("{}",e) //_ => {code}
},
type ExampleError2 {
arm ExampleError2::ThatError(8) => panic!("it was 8!"),
arm ExampleError2::ThatError(9) => 9,
_ => panic!("{}",e)
},
_ => panic!("{}",e) //what to do if error group isn't found
)
}
};
...
credit to this crate for the solution.
also, if anyone has any ideas on a better solution please let me know!

Related

Cleaner Match Arms for Deeply Nested Enums

I have the following function with a match expression:
fn handle_event<'e>(&mut self, event: Event<'e>) -> Event<'e> {
match (&event, &self.current_lang) {
(Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(lang))), _) => {
self.start_fenced_code_block(&lang)
}
(Event::End(Tag::CodeBlock(CodeBlockKind::Fenced(_))), _) => {
self.end_fenced_code_block()
}
(Event::Text(text), Some(lang)) => self.code_html(&text, &lang),
_ => event,
}
}
However, the first two arms felt like they were getting out of hand due to deeply nested enums. So I made some macros:
macro_rules! fenced_code_block_start {
($lang:pat_param) => {
Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced($lang)))
};
}
macro_rules! fenced_code_block_end {
() => {
Event::End(Tag::CodeBlock(CodeBlockKind::Fenced(_)))
};
}
and now I have the, IMHO, cleaner:
match (&event, &self.current_lang) {
(fenced_code_block_start!(lang), _) => self.start_fenced_code_block(&lang),
(fenced_code_block_end!(), _) => self.end_fenced_code_block(),
(Event::Text(text), Some(lang)) => self.code_html(&text, &lang),
_ => event,
}
However, I'm wondering if there's a better way to do this. Dropping down into macros always give me pause. Is there some other feature of Rust I can use here that isn't such a heavy hammer?
If clarity isn't lost, you can import and use the enum variants directly:
fn handle_event<'e>(&mut self, event: Event<'e>) -> Event<'e> {
use Event::*;
use Tag::*;
use CodeBlockKind::*;
match (&event, &self.current_lang) {
(Start(CodeBlock(Fenced(lang))), _) => self.start_fenced_code_block(&lang),
(End(CodeBlock(Fenced(_))), _) => self.end_fenced_code_block(),
(Text(text), Some(lang)) => self.code_html(&text, &lang),
_ => event,
}
}

Why do I get a "type annotations needed" error creating a notify watcher?

let mut watcher = notify::recommended_watcher(|res: Result<_>| {
match res {
Ok(event) => {
match event.kind {
EventKind::Modify(ModifyKind::Metadata(_)) => { /* deal with metadata */ }
EventKind::Create(CreateKind::File) => { /* deal with new files */ }
EventKind::Other => { /* ignore meta events */ }
_ => { /* something else changed */ }
}
},
Err(e) => println!("watch error: {:?}", e)
}
})
This code block gives me the error:
error[E0282]: type annotations needed
--> src/main.rs:13:23
|
13 | match event.kind {
| ^^^^^ cannot infer type
Rust Explorer
Which doesn't make sense to me because the docs for notify use the same code without specifying a type in the match.
Can anyone help explain what is going on and where I should specify which type to get this to work?
There are too many layers of indirection for Rust to infer what type the _ placeholder in Result<_> refers to. It will compile if you tell it _ means Event:
use notify::Event;
let mut watcher = notify::recommended_watcher(|res: Result<Event>| {
match res {
Ok(event) => {
match event.kind {
EventKind::Modify(ModifyKind::Metadata(_)) => { /* deal with metadata */ }
EventKind::Create(CreateKind::File) => { /* deal with new files */ }
EventKind::Other => { /* ignore meta events */ }
_ => { /* something else changed */ }
}
},
Err(e) => println!("watch error: {:?}", e)
}
})
Rust Explorer
The layers of indirection are:
The argument to recommended_watcher must be a type that implements EventHandler:
pub fn recommended_watcher<F>(event_handler: F) -> Result<RecommendedWatcher>
where
F: EventHandler,
A single-argument function that takes a Result<Event> and returns nothing is an EventHandler:
impl<F> EventHandler for F
where
F: FnMut(Result<Event>) + Send + 'static,
Therefore Result<_> must be Result<Event> to match the signature FnMut(Result<Event>).

How to express variable type in match arm?

I'm trying to parse a piece of json string using serde_json in Rust. I want to match the result of the parse using the following syntax:
match serde_json::from_str(msg.to_text().unwrap()) {
Ok(result) => {
println!("Parsed: {}", response.text);
}
Err(error) => {
println!("Failed to parse: {}", error);
}
}
but the compiler complains to me that he doesn't know the type of the result and of course, he is right. But how can I tell him the type of the result? I tried the following code, but it didn't work either. So I want to express the type of variable in the match arm.
match serde_json::from_str(msg.to_text().unwrap()) {
Ok(result: Response) => {
println!("Parsed: {}", response.text);
}
Err(error) => {
println!("Failed to parse: {}, {}", error, msg.to_text.unwrap());
}
}
There's a few way you can specify the type. Either you can explicitly specify it using type parameters in the function call, i.e. by using this syntax func::<T>().
match serde_json::from_str::<Response>(json) {
Ok(response) => {}
Err(err) => {}
}
Alternatively, you can assign the initial result to a variable, and hint the type there, i.e.
let res: Result<Response, _> = serde_json::from_str(json);
match res {
Ok(response) => {}
Err(err) => {}
}
or
match serde_json::from_str(json) {
Ok(response) => {
let response: Response = response;
}
Err(err) => {}
}
Lastly you can also use # bindings. However, this doesn't work if your type is an enum, as then you'd have to specify the exact variant you wanted.
As in, if Response was an enum you could have multiple Ok(resp # Response::???) match arms. But you couldn't have a single Ok(resp # Response) match arm.
match serde_json::from_str(json) {
Ok(response # Response { .. }) => {}
Err(err) => {}
}

Rust macro: capture exactly matching tokens

My goal is to write a macro expand! such that:
struct A;
struct B;
struct Mut<T>;
expand!() => ()
expand!(A) => (A,)
expand!(mut A) => (Mut<A>,)
expand!(A, mut B) => (A, Mut<B>,)
// etc
[Edit] added trailing comma for consistent tuple syntax.
I wrote this macro so far:
macro_rules! to_type {
( $ty:ty ) => { $ty };
( mut $ty:ty ) => { Mut<$ty> };
}
macro_rules! expand {
( $( $(mut)? $ty:ty ),* ) => {
(
$( to_type!($ty) ),*
,)
};
}
What I'm struggling with, is capturing the mut token. How can I assign it to a variable and reuse it in the macro body? Is it possible to work on more than 1 token at a time?
Something like this?
macro_rules! expand {
(#phase2($($ty_final:ty),*),) => {
($($ty_final,)*)
};
(#phase2($($ty_final:ty),*), mut $ty:ty, $($rest:tt)*) => {
expand!(#phase2($($ty_final,)* Mut::<$ty>), $($rest)*)
};
(#phase2($($ty_final:ty),*), $ty:ty, $($rest:tt)*) => {
expand!(#phase2($($ty_final,)* $ty), $($rest)*)
};
($($t:tt)*) => {
expand!(#phase2(), $($t)*)
};
}
struct A;
struct B;
struct Mut<T>(std::marker::PhantomData<T>);
fn main() {
#[allow(unused_parens)]
let _: expand!() = ();
#[allow(unused_parens)]
let _: expand!(A,) = (A,);
#[allow(unused_parens)]
let _: expand!(mut B,) = (Mut::<B>(Default::default()),);
#[allow(unused_parens)]
let _: expand!(A, mut B,) = (A, Mut::<B>(Default::default()));
}

How to write macro for similar matching arms?

I have some repetitive code
match *x {
A(ref a) => "special",
B(ref a) => "B foo",
C(ref a) => "C foo",
D(ref a) => "D foo",
// ...
}
I would like a macro like
macro_rules! generic_fmt {
($T:ident) => {
$T(ref a) => {"$T foo"},
}
}
So that I can simplify my matching to
match *x {
A(ref a) => "special",
generic_fmt!(B),
generic_fmt!(C),
generic_fmt!(D),
// ...
}
What's the best way to do that? I'm using rustc 1.19.0-nightly.
You cannot do that exactly. A macro cannot expand to a match arm (<Variant> => <Expression>).
The closest you could get is probably something like this:
enum Foo {
A(u32),
B(u32),
C(u32),
D(u32),
}
macro_rules! gen1 {
($Variant:ident) => {
Foo::$Variant(ref a)
}
}
macro_rules! gen2 {
($Variant:ident) => {
concat!(stringify!($Variant), " foo")
}
}
fn print(x: Foo) {
println!("{}", match x {
Foo::A(ref a) => "special",
gen1!(B) => gen2!(B),
gen1!(C) => gen2!(C),
gen1!(D) => gen2!(D),
});
}
fn main() {
print(Foo::A(42));
print(Foo::B(42));
print(Foo::C(42));
print(Foo::D(42));
}
Playground link.
If changing your output a little is acceptable, you could use a catch-all for all the same arms:
match x {
Foo::A(ref a) => println!("special"),
_ => println!("{:?} Foo", x),
}
Playground link
But this would print the type and its parameters. If you are on nightly, and not afraid of experimental, you can use std::intrinsics::type_name to display only the type name.
Or you can use a macro that does all your match arms:
macro_rules! gen_match {
($x:ident, $Special:ident, [$( $Foo:ident ),*]) => {
match $x {
Foo::$Special(ref a) => println!("special"),
$(Foo::$Foo(ref a) => println!("{} foo", stringify!($Foo)),)*
}
}
}
and calling it:
gen_match!(x, A, [B, C, D]);
Playground link
The brackets around the variants that will be generic formatted are for readability, they could be removed from macro definition

Resources