Is it actually not possible to create a macro like this or am I doing it wrong:
sample!("hello", "there") =>
println!("{:?}", "hello");
println!("{:?}", "there");
sample!("hello", "there", a "second type", a "another second type") =>
println!("{:?}", "hello");
println!("{:?}", "there");
println!("second {:?}", "second type");
println!("second {:?}", "another second type");
What I've tried is this (playground link):
macro_rules! sample {
(
$( $first:literal ),*
$( a $second:literal ),*
) => {
$(
println!("{:?}", $first);
)*
$(
println!("second {:?}", $second);
)*
};
}
Which fails with:
error: no rules expected the token `a`
--> main.rs:18:20
|
1 | macro_rules! sample {
| ------------------- when calling this macro
...
18 | sample!("hello", a "testing");
| ^ no rules expected this token in macro call
error: aborting due to previous error
Rust macros are really strict with their separators.
macro_rules! sample {
(
$( $first:literal, )*
$( a $second:literal ),*
) => {
$(println!("{:?}", $first);)*
$(println!("second {:?}", $second);)*
};
}
fn main() {
sample!("hello", a "testing");
}
This sample works, can you spot the change? I moved the comma from outside the first $( ... ) to inside. The difference is:
$( $a:literal ),* accepts only "a", "b", "c" (no trailing comma allowed)
$( $a:literal, )* accepts only "a", "b", "c", (trailing comma required)
In your macro, the in-between comma doesn't match as part of either the first or second repetition. The error is basically saying it expected another $first instead of a $second since that's what the repetition says.
You can potentially fix it by introducing an optional comma:
macro_rules! sample {
(
$( $first:literal ),*
$(,)? // <----------------
$( a $second:literal ),*
) => {
$(println!("{:?}", $first);)*
$(println!("second {:?}", $second);)*
};
}
which is more lenient but would allow weird things like this, which may or may not be okay depending on what you want.
sample!("hello", "there",);
sample!(, a "testing");
sample!("hello" a "testing");
Unfortunately, I don't know a perfect solution without using different arms like so:
macro_rules! sample {
($( $first:literal ),*) => { };
($( $first:literal, )* $( a $second:literal ),+) => { };
($( a $second:literal ),*) => { };
}
fn main() {
sample!("hello", "there");
sample!("hello", "there", a "testing");
sample!(a "second type", a "another second type");
// sample!("hello", "there",);
// sample!(, a "testing");
// sample!("hello" a "testing");
}
See also:
How do I create a Rust macro with optional parameters using repetitions?
How to allow optional trailing commas in macros?
You're doing it almost right- the trailing comma is tripping up the compiler. Removing the comma works just fine. If you are wondering why, it is because macro_rules invocations are quite picky. When it reads the trailing comma, it looks at the next token, which is a, but since that's not a literal, instead of doing the more sensible thing, which is checking the next pattern, it just gives up.
Related
I'd like to create a 'def_union' macro that creates a union-like enum with From support.
macro_rules! def_union
{
($name:ident $(< $( $param:tt $( : $bound_lt0:tt $(+ $bound_lt:tt )* )? ),+ >)?
{
$($type_name:ident : $type:ty),*
}) =>
{
enum $name $(< $( $param $( : $bound_lt0 $(+ $bound_lt )* )? ),+ >)?
{
$(
$type_name($type)
),*
}
$(
impl $(< $( $param:tt $( : $bound_lt0:tt $(+ $bound_lt:tt )* )? ),+ >)?
From<$type> for $name $(< $( $param $( : $bound_lt0 $(+ $bound_lt )* )? ),+ >)?
{
fn from(value: $type) -> $name
{
$name::$type_name(value)
}
}
)*
};
}
trait UnionValue {}
def_union!
(
UnionType<Value0: UnionValue, Value1: UnionValue, String>
{
Value0: Value0,
Value1: Value1,
Other: String
}
);
fn main()
{
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2c2f13988dc3b51431db4fe0b40a6706
Unfortunatelly the output is:
Compiling playground v0.0.1 (/playground)
error: meta-variable `param` repeats 1 time, but `r#type` repeats 3 times
--> src/main.rs:15:10
|
15 | $(
| __________^
16 | | impl $(< $( $param:tt $( : $bound_lt0:tt $(+ $bound_lt:tt )* )? ),+ >)?
17 | | From<$type> for $name $(< $( $param $( : $bound_lt0 $(+ $bound_lt )* )? ),+ >)?
18 | | {
... |
23 | | }
24 | | )*
| |_________^
error: could not compile `playground` due to previous error
The problem is with the generic parameters. I don't know a workaround.
EDIT: Added discriminator trait for the generic parameters.
I will assume you want to solve the error and not just implement particular macro.
To solve this error you may choose to use helper macro. The trick is not apparent at first so lets show the code. (with comments)
macro_rules! def_union {
(
$name:ident$(<$($param_name:ident$(($($param_bounds:tt)*))?),+>)? {
$($type_name:ident: $type:ty),*
}
) => {
enum $name $(<$($param_name$(: $($param_bounds)*)?),+>)? {
$(
$type_name($type)
),*
}
def_union!(#$name ($)
($(<$($param_name$(: $($param_bounds)*)?),+>)?)
($name $(<$($param_name),*>)?));
$(
$name!($type_name $type);
)*
};
// here we generate helper macro that will break the repetition paradox.
// all of the outer repeating parts are passed to later outputted macro
// the `$t` is used as escape token that allows us to output new macro
(#$name:ident ($t:tt) ($($generics:tt)*) ($($type:tt)*)) => {
macro_rules! $name {
($t name:ident $t type:ty) => {
impl$($generics)* From<$t type> for $($type)* {
fn from(value: $t type) -> Self {
Self::$t name(value)
}
}
};
}
};
}
trait UnionValue {}
// I removed generic parameters because they were conflicting each other.
// I also altered syntax a bit to simplify the macro, you can choose to
// make it more like rust syntax
def_union!(
UnionType<Value0(UnionValue)> {
Value0: Value0,
Value2: usize
}
);
fn main() {}
When trying to do
macro_rules! tipey {
(Vec<$pt: tt>) => { 2 };
(Vec<Option<$pt: tt>>) => { 3 };
($pt: tt) => { 1 };
}
macro_rules! structy {
(struct $i: ident {
$($p: ident: $pt: tt $(<$ppt: tt $(<$gt: tt> )? > )?),+
$(,)?
}) => {
const v: &[usize] = &[ $(tipey!( $pt $(<$ppt $(<$gt>)?>)?)),+ ];
};
}
structy!(
struct ContentDetails {
pattern: String,
fields: Vec<Option<String>>,
}
);
I get
How do I make this parse?
This problem is caused by your indiscriminate use of tt in your macros. This should be a last resort, as it can essentially match anything. The Rust compiler must disambiguate >> as part of a type v.s. expression based on the expected syntax. If you expect anything, then Rust must interpret >> as an operator.
Choose more appropriate metavariables such as ty (type), ident (identifier), path (fully specified path), etc.
I try to generate a struct from a macro looking like that:
decl_struct! {
MyStruct {
mut foo,
const bar,
}
}
The thing is that I want to filter out the fields marked as const. I have tried with the following code, but it does not function due to the fact that the outer macro is evaluated first:
macro_rules! decl_struct {
{
$name:ident {
$( $kind:ident $member:ident, )*
}
} => {
decl_struct!(#gen_struct $name { $( decl_struct!(#gen_struct_member $kind $member) )* } );
};
// Filters the `const` members out:
(#gen_struct_member mut $member:ident) => ($member,);
(#gen_struct_member const $member:ident) => ();
// Generate the whole struct:
{
#gen_struct $name:ident {
$( $member:ident, )*
}
} => {
struct $name {
$( $member:ident : u32 /*Dummy type*/, )*
}
};
}
decl_struct! {
MyStruct {
mut foo,
const bar,
}
}
Link to the playground.
What can be an alternative approach?
As documented in the reference:
Macros can expand to expressions, statements, items (including traits, impls, and foreign items), types, or patterns.
Notably, they can't expand to singular field definitions (as attempted by your #gen_struct_member invocations). However, for your particular problem, you can instead simplify to a single macro invocation with a bit of clever repetition:
macro_rules! decl_struct {
{
$name:ident {
$( const $initial:ident, )*
$(
mut $mut:ident,
$( const $const:ident, )*
)*
}
} => {
struct $name {$(
$mut: u32,
)*}
};
}
Playground.
I have an existing macro definition that captures the attributes. Inside that definition, I'm trying to filter and set the attributes selectively.
I have a peculiar problem where I have to set attributes for match arms in one of these cases.
macro_rules! set_mytrait {
(
$target:ident
$(
$( #[$cfgs:meta] )*
$field:ident
),*
$(,)*
) => {
impl $crate::MyTrait for $target
{
fn perform(&self) -> Option<&(dyn $crate::Error + 'static)> {
match self { $(
// Replace the below attributes with a filter function that returns only some of the attributes
$(#[$cfgs])*
$field => {
// doSomething($field);
None
}
)* }
}
}
}
}
I know that we cannot yet use macros in attributes according to this.
Apart from that, I've tried two options and both seem to have hit roadblocks.
Option 1
Approach: Using macro that takes just attributes as arguments and returns filtered attributes.
Implementation:
piped_attribs
#[macro_export]
macro_rules! piped_attribs {
(
$( $attr:tt )*
) => {
// Use filter logic here
$( #[$attr] )*
};
}
called_at
match self { $(
$crate::piped_attribs! { $( $cfgs ),* }
$field => {
// doSomething($field)
None
}
)* }
Problem: Seems like emitting attributes doesn't work in this context and I require to attach it to an item (source)
Option 2
Approach: Using macro that takes in two args. One for attributes and next for the match arm.
Implementation:
piped_attribs
#[macro_export]
macro_rules! piped_attribs {
(
[ $( $attr:tt )* ]
{ $rest: tt }
) => {
// Use filtering logic here
$( #[$attr] )*
$rest,
};
}
called at
match self { $(
$crate::piped_attribs! {
[$( $cfgs ),*]
{$field => {
// doSomething($field);
None
}}
}
)* }
Problem: I discovered that apparently we cannot return match "arms" from an attribute as of now. issue
Finally... the question
Is any of these approaches feasible and if not, is there a better way to look at this issue?
There are many question functions (hundreds), and each may have a different type. For each question I want to run a run_question function, which shows how long that function took, and print it's output.
I'm trying to shorten the following match expression with a Rust macro (writing run_question 100s of times does make the code rather long):
fn run_question<T: std::fmt::Display>(question_func: fn() -> T) {
let begin = Instant::now();
let output: T = question_func();
let elapsed_secs = begin.elapsed().as_micros() as f32 / 1e6;
println!("{}", output);
println!("{:.6}s taken", elapsed_secs);
}
fn q1() -> u8 { /* ... */ }
fn q2() -> u32 { /* ... */ }
fn q3() -> u64 { /* ... */ }
fn q4() -> String { /* ... */ }
fn main() {
// ...
match question_num {
1 => run_question(q1), 2 => run_question(q2), 3 => run_question(q3), 4 => run_question(q4),
_ => {
println!("Question doesn't exist.");
},
}
}
I have no experience in writing macros, and attempted the following which doesn't exactly work. It gives the error:
error: variable 'question_num' is still repeating at this depth
I'm rather stumped too how I can print the Question doesn't exist. as a default case.
#[macro_export]
macro_rules! run_questions {
( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
{
if $chosen_question == $question_num {
run_question($question_mod::solve);
}
}
};
}
The way I'd like to use it, is (or anything just as short is fine as well):
run_questions!(question_num, 1, q1, 2, q2, 3, q3, 4, q4);
I read a bit of the Rust book, but there aren't exactly that many examples of macros.
How would I go about doing this?
Rather than many if statements, I just reproduced the match statement
with a repetition $( ... )* for all the available branches.
It seems to behave like the extensive match expression.
macro_rules! run_questions {
( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
match $chosen_question {
$($question_num => run_question($question_mod),)*
_ => {
println!("Question doesn't exist.");
}
}
};
}
The error message explained:
macro_rules! run_questions {
($chosen_question: expr, $($question_num: expr, $question_mod: expr),*) => {{
In the above pattern you have a repetition with the * operator that involves variables $question_num and $question_mod
if $chosen_question == $question_num {
run_question($question_mod::solve);
}
In the corresponding code, you can't use $question_num and $question_mod directly: since they are repeated they potentially have more than one value and which one should the compiler use here? Instead, you need to tell the compiler to repeat the block of code that uses these variables. This is done by surrounding the repeated code block with $() and adding the * operator:
$(if $chosen_question == $question_num {
run_question($question_mod::solve);
})*
Although as pointed out by #prog-fh's answer, better to use a match in the macro, same as in the straight code:
match $chosen_question {
$($question_num => run_question ($question_mod::solve),)*
_ => println!("Question doesn't exist.")
};