Macros are like functions but only better. They are triggered based on their patterns using pattern matching for declarative Macros. Macros also group our codes into a unit (or units), and there are two types of macros in Rust – declarative and procedural. This brief post is about declarative macros.
The Simplest Pattern and Macro
The most straightforward macro does nothing!
1 2 3 4 5 6 7 | macro_rules! my_macro { () => {} } fn main() { my_macro!(); } |
Running the macro produces the following result.
The macro works because we used its name and the pattern that refers to a block of codes to execute. The pattern matching for the declarative macros worked!
Overloaded Pattern and Macro
A macro can be overloaded. It can have many blocks of codes based on pattern signatures we define. For instance, we have the following codes.
1 2 3 4 5 6 7 8 | macro_rules! my_macro { () => { println!("You are using the default pattern!"); }; (pattern1) => { println!("You are using the 'pattern1' pattern!"); }; (this_is_a_specific_pattern) => { println!("You are using the 'this_is_a_specific_pattern' pattern!"); }; } |
Now there are three blocks of codes. Each runs for a specific pattern, as shown in the following codes.
1 2 3 4 5 | fn main() { my_macro!(); my_macro!(pattern1); my_macro!(this_is_a_specific_pattern); } |
What if we run these codes?
1 2 3 | fn main() { my_macro!(while 1 = 1); } |
We will get these errors.
1 2 3 4 5 6 7 8 9 10 | error: no rules expected the token `while` --> src\main.rs:11:15 | 1 | macro_rules! my_macro { | --------------------- when calling this macro ... 11 | my_macro!(while 1 = 1); | ^^^^^ no rules expected this token in macro call error: aborting due to previous error |
my_macro does not have that pattern defined for a block of codes.
Pattern Matching for Macro Parameters
The macro we have so far does not use any data outside itself. We need to pass data to it. So how to do that? Modify the pattern by prefixing a token with a $ sign followed by “:” and specific type. A type can be either the following.
- ident: an identifier. Examples: x; foo.
- path: a qualified name. Example: T::SpecialA.
- expr: an expression. Examples: 2 + 2; if true { 1 } else { 2 }; f(42).
- ty: a type. Examples: i32; Vec<(char, String)>; &T.
- pat: a pattern. Examples: Some(t); (17, ‘a’); _.
- stmt: a single statement. Example: let x = 3.
- block: a brace-delimited sequence of statements. Example: { log(error, “hi”); return 12; }.
- item: an item. Examples: fn foo() { }; struct Bar;.
- meta: a “meta item”, as found in attributes. Example: cfg(target_os = “windows”).
- tt: a single token tree.
We can come up with the following codes based on the previous ones.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | macro_rules! my_macro { ($data:expr) => { println!("You are using the default pattern! Data is {}", $data); }; (pattern1 $data:expr) => { println!("You are using the 'pattern1' pattern! Data is {}", $data); }; ($data:expr, this_is_a_specific_pattern) => { println!("You are using the 'this_is_a_specific_pattern' pattern! Data is {}", $data); }; } fn main() { my_macro!("ABC"); my_macro!(pattern1 "DEF" ); my_macro!("GEF", this_is_a_specific_pattern); } |
And this is the output:
1 2 3 | You are using the default pattern! Data is ABC You are using the 'pattern1' pattern! Data is DEF You are using the 'this_is_a_specific_pattern' pattern! Data is GEF |
Again, the pattern matching for declarative macros worked!
Tested with Rust 1.40.0.
For more information, please see https://doc.rust-lang.org/book/ch19-06-macros.html.