How to write macro for similar matching arms? - rust

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

Related

How to define functions for specific variants

I have an enum with an associated show function.
enum Foo {
Bar(u32),
Baz
}
fn show(foo: Foo) {
match foo {
Foo::Bar(_) => show_bar(foo),
Foo::Baz => show_baz(foo),
}
}
Based on the variant, a specialized show_* function is called.
fn show_bar(foo: Foo) {
match foo {
Foo::Bar(x) => println!("BAR({x})"),
_ => (),
}
}
fn show_baz(foo: Foo) {
match foo {
Foo::Baz => println!("BAZ"),
_ => (),
}
}
Since I can't pass variants as types, I need to repeat the match foo pattern matching inside each show_* function, which is a bit verbose and undeeded.
Is there a better / more idiomatic way to do this?
I'm a fairly simple soul and not a Rust expert, so maybe this doesn't meet your needs for some reason - but why not simply pass the associated data? (Which is nothing in the case of Baz)
enum Foo {
Bar(u32),
Baz
}
fn show(foo: Foo) {
match foo {
Foo::Bar(barValue) => show_bar(barValue),
Foo::Baz => show_baz(),
}
}
fn show_bar(x: u32) {
println!("BAR({x})"),
}
fn show_baz() {
println!("BAZ"),
}

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,
}
}

Rust macro to format arguments over multiple formats

TL;DR
I'm trying to write a macro that will do the following transformation:
magic_formatter!(["_{}", "{}_", "_{}_"], "foo") ==
[format!("_{}", "foo"),
format!("{}_", "foo"),
format!("_{}_", "foo")]
(a solution that will give ["_foo", "foo_", "_foo_"] and works for varargs is also welcome)
Full story:
I'm writing a parser, and many of it's tests do stuff like this:
let ident = identifier().parse("foo ").unwrap();
assert_eq!(ident, Syntax::ident("foo"));
let ident = identifier().parse(" foo").unwrap();
assert_eq!(ident, Syntax::ident("foo"));
let ident = identifier().parse(" foo ").unwrap();
assert_eq!(ident, Syntax::ident("foo"));
so I tried to reduce repetition by doing this:
for f in [" {}", "{} ", " {} "] {
let inp = format!(f, "foo");
let ident = identifier().parse(inp).unwrap();
assert_eq!(ident, Syntax::ident("foo"));
}
which of course doesn't compile.
However, it seems to me that there isn't really any unknown information preventing from the whole array to be generated at compile time, so I searched the webz, hoping that this has been solved somewhere already, but my google-fu can't seem to find anything that just does what I want.
So I thought I'd get my hands dirty and write an actually useful rust macro for the first time(!).
I read the macro chapter of Rust by Example, and failed for a while.
Then I tried reading the actual reference which I feel that got me a few steps further but I still couldn't get it right.
Then I really got into it and found this cool explanation and thought that I actually had it this time, but I still can't seem to get my macro to work properly and compile at the same time.
my latest attempt looks is this:
macro_rules! map_fmt {
(#accum () -> $($body:tt),*) => { map_fmt!(#as_expr [$($body),*]) };
(#accum ([$f:literal, $($fs:literal),*], $args:tt) -> $($body:tt),*) => {
map_fmt!(#accum ([$($fs),*], $args) -> (format!($f, $args) $($body),*))
};
(#as_expr $e:expr) => { $e };
([$f:literal, $($fs:literal),*], $args:expr) => {
map_fmt!(#accum ([$f, $($fs),*], $args) -> ())
};
}
I'll appreciate if someone could help me understand what is my macro missing? and how to fix it, if even possible? and if not is there some other technique I could/should use to reduce the repetition in my tests?
Edit:
this is the final solution I'm using, which is the correct answer provided by #finomnis, which I slightly modified to support variadic arguments in the format! expression
macro_rules! map_fmt {
(#accum ([$f:literal], $($args:tt),*) -> ($($body:tt)*)) => { [$($body)* format!($f, $($args),*)] };
(#accum ([$f:literal, $($fs:literal),*], $($args:tt),*) -> ($($body:tt)*)) => {
map_fmt!(#accum ([$($fs),*], $($args),*) -> ($($body)* format!($f, $($args),*),))
};
([$f:literal, $($fs:literal),*], $($args:expr),*) => {
map_fmt!(#accum ([$f, $($fs),*], $($args),*) -> ())
};
}
format!() doesn't work, because it generates the code at compiletime and therefore needs an actual string literal formatter.
str::replace(), however, works:
fn main() {
for f in [" {}", "{} ", " {} "] {
let inp = f.replace("{}", "foo");
println!("{:?}", inp);
}
}
" foo"
"foo "
" foo "
I don't think there is any reason why doing this at runtime is a problem, especially as your format!() call in the macro is also a runtime replacement, but nonetheless I think this is an interesting challenge to learn more about macros.
There are a couple of problems with your macro.
For one, the () case should be ([], $_:tt) instead.
But the main problem with your macro is that [$f:literal, $($fs:literal),*] does not match [""] (the case where only one literal is left) because it doesn't match the required comma. This one would match: ["",].
This can be solved by converting the $(),* into $(),+ (meaning, they have to carry at least one element) and then replacing the [] (no elements left) case with [$f:literal] (one element left). This then handles the special case where only one element is left and the comma doesn't match.
The way you select your intermediate results has minor bugs in several places. At some places, you forgot the () around it, and the arguments may be in the wrong order. Further, it's better to transport them as $(tt)* instead of $(tt),*, as the tt contains the comma already.
Your $as_expr case doesn't serve much purpose according to the newer macro book, so I would remove it.
This is how your code could look like after fixing all those things:
macro_rules! map_fmt {
(#accum ([$f:literal], $args:tt) -> ($($body:tt)*)) => {
[$($body)* format!($f, $args)]
};
(#accum ([$f:literal, $($fs:literal),*], $args:tt) -> ($($body:tt)*)) => {
map_fmt!(#accum ([$($fs),*], $args) -> ($($body)* format!($f, $args),))
};
([$f:literal, $($fs:literal),*], $args:expr) => {
map_fmt!(#accum ([$f, $($fs),*], $args) -> ())
};
}
fn main() {
let fmt = map_fmt!(["_{}", "{}_", "_{}_"], "foo");
println!("{:?}", fmt);
}
["_foo", "foo_", "_foo_"]
However, if you use cargo expand to print what the macro resolves to, this is what you get:
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
fn main() {
let fmt = [
{
let res = ::alloc::fmt::format(::core::fmt::Arguments::new_v1(
&["_"],
&[::core::fmt::ArgumentV1::new_display(&"foo")],
));
res
},
{
let res = ::alloc::fmt::format(::core::fmt::Arguments::new_v1(
&["", "_"],
&[::core::fmt::ArgumentV1::new_display(&"foo")],
));
res
},
{
let res = ::alloc::fmt::format(::core::fmt::Arguments::new_v1(
&["_", "_"],
&[::core::fmt::ArgumentV1::new_display(&"foo")],
));
res
},
];
{
::std::io::_print(::core::fmt::Arguments::new_v1(
&["", "\n"],
&[::core::fmt::ArgumentV1::new_debug(&fmt)],
));
};
}
What you can clearly see here is that the format! is still a runtime call. So I don't think that the macro actually creates any kind of speedup.
You could fix that with the const_format crate:
macro_rules! map_fmt {
(#accum ([$f:literal], $args:tt) -> ($($body:tt)*)) => {
[$($body)* ::const_format::formatcp!($f, $args)]
};
(#accum ([$f:literal, $($fs:literal),*], $args:tt) -> ($($body:tt)*)) => {
map_fmt!(#accum ([$($fs),*], $args) -> ($($body)* ::const_format::formatcp!($f, $args),))
};
([$f:literal, $($fs:literal),*], $args:expr) => {{
map_fmt!(#accum ([$f, $($fs),*], $args) -> ())
}};
}
fn main() {
let fmt = map_fmt!(["_{}", "{}_", "_{}_"], "foo");
println!("{:?}", fmt);
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>())
}
print_type_of(&fmt);
}
["_foo", "foo_", "_foo_"]
[&str; 3]
You can now see that the type is &'static str, meaning, it is now being formatted at compile time and stored in the binary as a static string.
That all said, I think the entire recursion in the macro is quite pointless. It seems like it can be done with a single repetition:
macro_rules! map_fmt {
([$($fs:literal),*], $args:expr) => {{
[$(format!($fs, $args)),*]
}};
}
fn main() {
let fmt = map_fmt!(["_{}", "{}_", "_{}_"], "foo");
println!("{:?}", fmt);
}
["_foo", "foo_", "_foo_"]
If you want to support an arbitrary number of arguments for format!(), then you could do:
macro_rules! map_fmt {
(#format $f:literal, ($($args:expr),*)) => {
format!($f, $($args),*)
};
([$($fs:literal),*], $args:tt) => {{
[$(map_fmt!(#format $fs, $args)),*]
}};
}
fn main() {
let fmt = map_fmt!(["_{}_{}", "{}__{}", "{}_{}_"], ("foo", "bar"));
println!("{:?}", fmt);
}
["_foo_bar", "foo__bar", "foo_bar_"]

match multiple enum types in macro

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!

How can I get "Bar" from "Foo::Bar" in rust macro?

Original demand: I want to implement a macro that converts Foo::* to Bar::*.
Pseudo code will look like this:
macro_rules! convert_foo_to_bar {
($v: ty, $p: path) => (<$v>::$p.name)
}
// convert_foo_to_bar!(Bar, Foo::A) -> Bar::A
While $p.name refers to A.
You can match the Foo::A using Foo::$variant:ident to get A as $variant like this:
macro_rules! convert_foo_to_bar {
($v: ty, Foo::$variant:ident) => (<$v>::$variant)
}
Playground
If you need to convert a variable, you will need to use a normal function such as this:
fn convert_foo_to_bar(foo: Foo) -> Bar {
match foo {
Foo::A => Bar::A,
Foo::B => Bar::B,
// .. for all of your variants
}
}

Resources