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?
Related
I'm just getting going with Rust, and am trying to smooth over a couple inconveniences using macros. The first is that the derive Defaults macros don't support const functions so they don't support static objects. For this, I got help and adapted this macro I was given, it works well:
#[macro_export]
macro_rules! define_struct_with_const_defaults {
($v:vis struct $name:ident { $($f_name:ident: $f_type:ty = $f_default:expr),* $(,)? }) => {
#[derive(Clone,Copy)]
$v struct $name {
$(
$f_name: $f_type,
)*
}
impl $name {
pub const fn new() -> $name {
$name {
$(
$f_name: $f_default,
)*
}
}
}
}
}
So the next thing I wanted to do was get painless structure access for unit testing with python going. So I thought I could do something like this:
use pyo3::prelude::*;
#[macro_export]
macro_rules! define_struct_with_const_defaults {
($v:vis struct $name:ident { $($f_name:ident: $f_type:ty = $f_default:expr),* $(,)? }) => {
#[derive(Clone,Copy)]
#[pyclass]
$v struct $name {
$(
#[pyo3(get, set)] $f_name: $f_type,
)*
}
impl $name {
pub const fn new() -> $name {
$name {
$(
$f_name: $f_default,
)*
}
}
}
}
}
But that doesn't work, the complaint is "cannot find attribute pyclass in this scope"
On the other hand, if I do this, it works:
use pyo3::prelude::*;
#[pyclass]
struct bar {
baz: i32,
}
This is beyond my level of understanding. It seems to be related to nesting macros, I'm wondering whether it has something to do with order of evaluation.
Eventually, I'd like to conditionally compile the pyo3 stuff with a config macro, depending on whether I'm in a unit test environment, but I can't even get this far ...
Any help would be appreciated in understanding what is going on here.
The problem is probably that the macro will insert #[pyclass] at the callsite of it. So you have to put your use pyo3::prelude::*; there or even better include the full path in the macro to avoid name conflicts:
#[macro_export]
macro_rules! define_struct_with_const_defaults {
($v:vis struct $name:ident { $($f_name:ident: $f_type:ty = $f_default:expr),* $(,)? }) => {
#[derive(Clone,Copy)]
#[::pyo3::prelude::pyclass]
$v struct $name {
$(
#[pyo3(get, set)] $f_name: $f_type,
)*
}
impl $name {
pub const fn new() -> $name {
$name {
$(
$f_name: $f_default,
)*
}
}
}
}
}
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.
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.
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.")
};
Take a look at this implementation:
impl consensus::Trait for Runtime {
type Log = Log;
type SessionKey = AuthorityId;
// The Aura module handles offline-reports internally
// rather than using an explicit report system.
type InherentOfflineReport = ();
}
How is Log defined? There is no use clause for importing this symbol.
Running
cargo rustc -- -Z unstable-options --pretty=expanded
does not show any Log entry with type clause. It does show other macro declarations after expanding macros at level 0, but I am not sure if this relevant.
I tried using the Atom IDE because it automatically parses the files and lets you find the definition of the symbols, but it did not help.
How can I find how Log is defined?
Log is defined by construct_runtime macro.
Here are some relevant code:
construct_runtime macro:
https://github.com/paritytech/substrate/blob/950e90e75dc7d16dcf99972fcc733945a832dc3e/srml/support/src/runtime.rs#L79
macro_rules! construct_runtime {
(
pub enum $runtime:ident with Log ($log_internal:ident: DigestItem<$( $log_genarg:ty ),+>)
where
Block = $block:ident,
NodeBlock = $node_block:ty,
UncheckedExtrinsic = $uncheckedextrinsic:ident
{
$( $rest:tt )*
}
)
calling __decl_outer_log
https://github.com/paritytech/substrate/blob/950e90e75dc7d16dcf99972fcc733945a832dc3e/srml/support/src/runtime.rs#L267
$crate::__decl_outer_log!(
$runtime;
$log_internal < $( $log_genarg ),* >;
{};
$(
$name: $module:: $( < $module_instance >:: )? { $( $modules $( ( $( $modules_args )* ) )* )* }
)*
);
__decl_outer_log macro
https://github.com/paritytech/substrate/blob/950e90e75dc7d16dcf99972fcc733945a832dc3e/srml/support/src/runtime.rs#L706
macro_rules! __decl_outer_log {
(
$runtime:ident;
$log_internal:ident <$( $log_genarg:ty ),+>;
{ $( $parsed:tt )* };
$name:ident: $module:ident:: $(<$module_instance:ident>::)? {
Log ( $( $args:ident )* ) $( $modules:ident $( ( $( $modules_args:ident )* ) )* )*
}
$( $rest:tt )*
) => {
calling impl_outer_log
https://github.com/paritytech/substrate/blob/950e90e75dc7d16dcf99972fcc733945a832dc3e/srml/support/src/runtime.rs#L763
(
$runtime:ident;
$log_internal:ident <$( $log_genarg:ty ),+>;
{ $(
$parsed_modules:ident $(< $parsed_instance:ident >)? ( $( $parsed_args:ident )* )
)* };
) => {
$crate::paste::item! {
$crate::runtime_primitives::impl_outer_log!(
pub enum Log($log_internal: DigestItem<$( $log_genarg ),*>) for $runtime {
$( [< $parsed_modules $(_ $parsed_instance)? >] $(< $parsed_modules::$parsed_instance >)? ( $( $parsed_args ),* ) ),*
}
);
}
};
impl_outer_log macro:
https://github.com/paritytech/substrate/blob/950e90e75dc7d16dcf99972fcc733945a832dc3e/core/sr-primitives/src/lib.rs#L630
macro_rules! impl_outer_log {
(
$(#[$attr:meta])*
pub enum $name:ident ($internal:ident: DigestItem<$( $genarg:ty ),*>) for $trait:ident {
$( $module:ident $(<$instance:path>)? ( $( $sitem:ident ),* ) ),*
}
)
which actually declare and implement the Log struct
You should be able to see the result when cargo expand the runtime crate.