Macros
is a Rust way of metaprogramming
- writing a code that creates another code.
println!
and vec!
are examples of macroses. Internally they execute more code, that has to be
written manually. e.g. with vec!
it is possible to create an instance of Vec<T>
and add some
values inside of it during single statement.
Macros are similar to functions, but the bigges difference between them in moment of execution -
function
is executed in runtime, whereas macros
is executed during compile time. That quality
allows for example implement trait for some type inside of a macros
.
The main con for macros
is higher complexity to write and read. Also macros have to be defined
strictly before place, where it is used.
Macros types in Rust:
- declarative - declared with
macro_rules!
- procedural:
-
Custom
#[derive]
macros that specify code added with the derive attribute used on structs and enums Attribute-like
macros that define custom attributes usable on any item-
Function-like
macros that look like function calls but operate on the tokens specified as their argument
-
Custom
Declarative macros
Declarative macros
are something similar to match
expressions, which allows to get values,
compare it with some value and then execute required branch of code and do it on compilation stage.
declarative macros
are defined with macro_rules!
.
Consider for example vec!
macro, which allows to create filled Vec<T>
instance:
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
#[macro_export]
allows to import created macro into usage scope. macro_rules!
is a beginning of
macro definition. After we define the name of the macro and then starts the scope of the macro.
Macro definition has a structure similar to match
expresstion, where it is possible to define
several arm cases with pattern on the left side and executable code on the right.
Here ( $( $x:expr ),* )
is a pattern. When user enters inside of a macro value data, which will
match this pattern, the code, assosiated with it will be executed.
Pattern syntax:
Pattern begins with (...)
. Inside $(...)
captures entered value. $x:expr
defines a name $x
for captured value. expr
is a Rust expression. ,
indicates, that after captured expression
comma character may appear. And finally *
indicates, that this patter may be repetetive.
So for vec![1, 2, 3];
this pattern will capture numbers 1, 2 and 3 repetitively.
Inside of arm executable code we defined $(...)*
, which means that code inside of it will be
emmitted for every $( $x:expr )
pattern match.
That way, after this macro will generate actual Rust code, it will emit:
{
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
}