Macro to handle a literal string

I have this piece of code which calls a call_init! macro that I'd like to implement by using a macro in place of the first argument:

#[cfg(custom)]
call_init!("set1",  [f1, f2]);
#[cfg(not(custom))]
call_init!("set2",  [f1, f2]);

So, I tried:

macro_rules! name {
  #[cfg(custom)]
  ($a1:literal, $a2:literal) => {$a1};
  #[cfg(not(custom))]
  ($a1:literal, $a2:literal) => {$a2};
}

call_init!(name!("set1", "set2"),  [f1, f2]);

However, I get an error:

error: expected literal
  --> src/lib.rs:83:5
   |
83 |     name!("set1", "set2"),
   |     ^^^^

warning: unused macro definition
  --> src/lib.rs:77:1
   |
77 | / macro_rules! name {
...

Any idea how to define the macro name!?

Your issue isn't with name!(), but with call_init!(). The call_init!() macro is defined to accept a string literal as its first argument but you've actually passed it the tokens name, !, (, "set1", ,, "set2", and ). That's because macros are evaluated from the outside-in and not the inside-out like expressions.

Something you might try is the callback pattern. This is where you define a macro which takes the name of a macro to invoke and its arguments.

Here's a simple example:

macro_rules! call_it {
    ($macro_name:ident, $($args:tt)*) =>  {
        $macro_name!( $($args)* )
    }
}

fn main() {
    call_it!(println, "Hello, {}", "world");
}

(playground)

In your case you want to combine the callback pattern with some form of iteration, either the normal $(...)* or recursion.

For my implementation I'm just using a call_init!() that prints out any arguments you gave it.

macro_rules! call_init {
    ($($token:tt)*) =>  {
        println!(concat!(
            "Called with {{ ",
            $( stringify!($token), " "  ),*,
            "}}"
        ));
    }
}

This is my version of name!().

macro_rules! name {
    (
        // the name of the macro we are going to invoke
        $macro_name:ident,
        // We'll invoke the macro once for each of these arguments
        [
            // Peel off the first one
            $first_variable_arg:literal
            // And save the rest to the $variable_args sequence
            $(, $variable_args:tt),*
        ],
        // Stash away the arguments we are going to use every time
        [$($fixed_args:tt),*]
    ) => {
            // Invoke the macro using our first variable argument
            $macro_name!($first_variable_arg, [$($fixed_args),*]);
            // then recurse so we can handle the other variable arguments
            name!($macro_name, [$($variable_args),*], [$($fixed_args),*]);
    };
    // Do nothing because there are no more variable arguments
    ($macro_name:ident, [], [$($fixed_args:tt),*]) => {};
}

And here is how it is used:

fn main() {
    // call_init!("set1",  [f1, f2]);
    // call_init!("set2",  [f1, f2]);

    name!(call_init, ["set1", "set2"], [f1, f2]);
}

(playground)

1 Like

Another, approach to do a callback goes like this, using a macro-definition inside of a macro-definition

macro_rules! name {
    ($lit1:literal, $lit2: literal, |$dollar:tt $X:ident| $($t:tt)*) => {
        // extra block to keep the helper macro limited in scope
        {
            macro_rules! helper {
                ($dollar$X:literal) => {
                    $($t)*
                }
            }
            // can't directly write `$ $X: ident` above, so this workaround
            // ensures that $dollar is a dollar sign token
            macro_rules! ensure_dollar {
                ($) => {}
            }
            ensure_dollar!{$dollar}
    
            #[cfg(custom)]
            helper!{$lit1}
            #[cfg(not(custom))]
            helper!{$lit2}
        }
    }
}

This macro can be called with a closure-like syntax

name!("set1", "set2", |$X| call_init!($X, [f1, f2]));
note: trace_macro
  --> src/lib.rs:34:5
   |
34 |     name!("set1", "set2", |$X| call_init!($X, [f1, f2]));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: expanding `name! { "set1", "set2", | $X | call_init! ($X, [f1, f2]) }`
   = note: to `{
               macro_rules! helper { ($X : literal) => { call_init! ($X, [f1, f2]) } }
               macro_rules! ensure_dollar { ($) => { } } ensure_dollar! { $ }
               #[cfg(custom)] helper! { "set1" } #[cfg(not(custom))] helper! { "set2" }
           }`
   = note: expanding `ensure_dollar! { $ }`
   = note: to ``
   = note: expanding `helper! { "set2" }`
   = note: to `call_init! ("set2", [f1, f2])`
   = note: expanding `call_init! { "set2", [f1, f2] }`

(in the playground)

Thank you, steffahn, for a very detailed explanation!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.