Reputation: 927
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
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 tt
s: $($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));
Upvotes: 5