LightSith
LightSith

Reputation: 927

pass macro arguments to other macro

I am new to rust. I am trying to create macro which takes a buffer and then decodes some data out of it and creates givens list of variables. if error occurs then it should print error and continue since I'm gonna call it in a loop where I receive buffers. something like this:-

for bin_ref in bufs {

   extract!( bin_ref anime &str episodes u32 season u32);

   //if everything goes ok then do some cool stuff with
   //above variables otherwise take next buf_ref    
}

How can I do this? So for I came with this aproach:-

#[macro_export]
macro_rules! extract {

    ( $buf:ident $($var:ident $typ:ty),* ) => {
        $(
            ext_type!( $buf $var $typ );
        )*
    };
}

#[macro_export]
macro_rules! ext_type {
    ( $buf:ident $var:ident &str ) => {

        let mut $var : &str = ""; //some string specific function
        println!("doing cool things with '{}' which is string ",$var);        

    };
    ( $buf:ident $var:ident u32 ) => {
        let mut $var : u32 = 34; //some u32 specific function
        println!("doing cool things with '{}' which is u32",$var);
    }
}

I have following test function:-

fn macro_test() {

    let mut bin_ref : &[u8] = &[0u8;100];

    ext_type!(bin_ref anime &str); // works
    ext_type!(bin_ref episodes u32 ); // works

    extract!( bin_ref username &str, password &str ); // does not work. why ??
}

When I compile this,I get following error:-

error: no rules expected the token `&str`
  --> src/easycode.rs:11:34
   |
11 |             ext_type!( $buf $var $typ );
   |                                  ^^^^ no rules expected this token in macro call
...
19 | macro_rules! ext_type {
   | --------------------- when calling this macro
...
48 |     extract!( bin_ref username &str, password &str );
   |     ------------------------------------------------- in this macro invocation

Why I cant just pass $typ to ext_type! macro? it works when called from code

Upvotes: 2

Views: 3144

Answers (1)

EvilTak
EvilTak

Reputation: 7579

The ext_type! macro's rules require the literal tokens &str and u32 at the end. These literal tokens cannot match the matched fragment $typ:ty in extract!. In order to successfully match the literal tokens to a matched fragment, it must be a tt, ident or lifetime.

The only option that will work in this case is tt, which simply put, is just a parser token. A type is often composed of more than one token though; case in point &str, which consists of two tokens & and str. We must thus use a repetition to fully capture a type with tts: $($typ:tt)+ will do nicely.

Using an unbounded repetition with tt comes at a cost, however -- a tt will match almost everything, so simply substituting $typ:ty with $($typ:tt)+ will not work, as the $typ repetition will capture everything till the end of the macro invocation! To prevent this from happening, we must delimit the type token tree in the macro rule matcher to stop it from consuming everything. At the cost of making invocation slightly verbose, wrapping the repetition in parentheses in will serve us well and stop the token tree matching exactly where we want it to. The modified macro looks like this:

#[macro_export]
macro_rules! extract {
    ( $buf:ident $($var:ident ($($typ:tt)+)),* ) => {
        $(
            ext_type!( $buf $var $($typ)+);
        )*
    };
}

Note the replacement of $typ:ty with ($($typ:tt)+) (which is a token tree repetition wrapped in parentheses) in the matcher, and the replacement of $typ with $($typ)+ in the transcriber.

The macro rule is invoked as follows:

extract!(bin_ref username (&str), password (&str), id (u32));

Rust Playground

Upvotes: 5

Related Questions