34
loading...
This website collects cookies to deliver better user experience
vec!
, println!
and format!
. The actual coding start in the next section.
vec!
and println!
, respectively). These are (two of) the reasons to use macros, but what about the reasons to create them? path!
. Or maybe you want to use a macro as a boilerplate, so you don't have to create several similar functions, as I did here. It might be also the case that you need something that cannot be delivered by usual Rust syntax, like a function with initial values or structurally different parameters (such as vec!
, that allows calls like vec![2,2,2]
or vec![2;3]
—more on this later).macro_rules! etwas {
() => {}
}
etwas!()
, etwas![]
or etwas!{}
. There's no way to force one of those. When we call a macro always using one or the other—like parenthesis in println!("text")
or square-brackets in vec![]
—it is just a convention of usage (a convention that we should keep).macro_rules! double {
($value:expr) => { $value * 2 }
}
fn main() {
println!("{}", double!(7));
}
=>
is the matcher, the rules that define what the macro can receive as input. The right side is the transcriber, the output processing. Not very important, but both matcher and transcriber could be writen using either ()
, []
or {}
.
$
(e.g., $value:expr
). Their structure is: $
name
:
designator
. $
and :
are fixed.name
follows Rust variables convention. When used in the transcriber (see below), they will be called metavariables.expr
), since Rust is “primarily an expression language”. A list of possible designators can be found here. Note: There seems to be no consensus on the name "designator". The little book calls it "capture"; The Rust reference calls it "fragment-specifier"; and you will also find people referring them as "types". Just be aware of that when jumping from source to source. Here, I will stick with designator, as proposed in Rust by example.
$
. For example:macro_rules! power {
($value:expr, squared) => { $value.pow(2) }
}
fn main() {
println!("{}", power!(3_i32, squared));
}
=>
, ,
or ;
. That is why I had to add a comma between $value:expr
and the fixed-value squared
. You will find a complete list of follow-ups here.macro_rules! power {
($value:expr, squared) => { $value.pow(2_i32) }
($value:expr, cubed) => { $value.pow(3_i32) }
}
vec![2]
or vec![1, 2, 3]
. This is where the matching resembles Regex the most. Basically, we wrap the variable inside $()
and follow up with a repetition operator:*
— indicates any number of repetitions.+
— indicates any number, but at least one.?
— indicates an optional, with zero or one occurrence.n
numbers. We need at least two addends, so we will have a single first value, and one or more (+
) second value. This is what such a matching would look like.macro_rules! adder {
($left:expr, $($right:expr),+) => {}
}
fn main() {
adder!(1, 2, 3, 4);
}
+
. That's how we add a separator for each repetition without a trailing separator. But what if we want a trailing separator? Or maybe we want it to be flexible, allowing the user to have a trailing separator or not? You may have any of the three possibilities like this:macro_rules! no_trailing {
($($e:expr),*) => {}
}
macro_rules! with_trailing {
($($e:expr,)*) => {}
}
macro_rules! either {
($($e:expr),* $(,)*) => {}
}
fn main() {
no_trailing!(1, 2, 3);
with_trailing!(1, 2, 3,);
either!(1, 2, 3);
either!(1, 2, 3,);
}
vec!
macro example. For that, I will omit the transcriber.macro_rules! vec {
() => {};
($elem:expr; $n:expr) => {};
($($x:expr),+ $(,)?) => {};
}
vec![]
, which creates an empty Vector.vec!["text"; 10]
, which repeats the first value ("text") n
times, where n
is the second value (10).vec![1,2,3]
, which creates a vector with all the listed elements.If you want to see the implementation of the vec!
macro, check Jon's stream about macros.
=>
. Most of what you are going to do here is regular Rust, but I would like to bring your attention to some specificities.power!
, I did this:power!(3_i32, squared);
i32
because I used the pow()
function, which cannot be called on ambiguous numeric type; and as we do not define types in macros, I had to let the compiler know this information somehow. This is something to be aware when dealing with macros. Of course, I could have forced it by declaring a variable and passing the metavariable value to it and thus fixing the variable type. However, to do such a thing, we need multiple statements.macro_rules! etwas {
//v --- this one
($value:expr, squared) => {{
let x: u32 = $value;
x.pow(2)
}}
//^ --- and this one
};
adder!
macro.macro_rules! adder {
($($right:expr),+) => {{
let mut total: i32 = 0;
$(
total += $right;
)+
total
}};
}
fn main() {
assert_eq!(adder!(1, 2, 3, 4), 10);
}
$()+
(the repetition operator should match, that is why I am using +
here as well). macro_rules! operations {
(add $($addend:expr),+; mult $($multiplier:expr),+) => {{
let mut sum = 0;
$(
sum += $addend;
)*
let mut product = 1;
$(
product *= $multiplier;
)*
println!("Sum: {} | Product: {}", sum, product);
}}
}
fn main() {
operations!(add 1, 2, 3, 4; mult 2, 3, 10);
}
macro_rules! operations {
(add $($addend:expr),+; mult $($multiplier:expr),+) => {{
let mut sum = 0;
let mut product = 1;
$(
sum += $addend;
product *= $multiplier;
)*
println!("Sum: {} | Product: {}", sum, product);
}}
}
error: meta-variable 'addend' repeats 4 times, but 'multiplier' repeats 3 times
--> src/main.rs:43:10
|
43 | $(
| __________^
44 | | sum += $addend;
45 | | product *= $multiplier;
46 | | )*
| |_________^
$ cargo rustc --profile=check -- -Zunstable-options --pretty=expanded
cargo-expand
:$ cargo install cargo-expand
cargo expand
.Note: Although you don't have to be using the nightly compiler, I guess (and you may call me on this) you got to have it installed. To do so, run the command rustup instal nightly
.
operations!
is expanded.fn main() {
{
let mut sum = 0;
sum += 1;
sum += 2;
sum += 3;
sum += 4;
let mut product = 1;
product *= 2;
product *= 3;
product *= 10;
{
::std::io::_print(::core::fmt::Arguments::new_v1(
&["Sum: ", " | Product: ", "\n"],
&match (&sum, &product) {
(arg0, arg1) => [
::core::fmt::ArgumentV1::new(arg0, ::core::fmt::Display::fmt),
::core::fmt::ArgumentV1::new(arg1, ::core::fmt::Display::fmt),
],
},
));
};
};
}
println!
was expanded.#[macro_export]
.#[macro_export]
macro_rules! etwas {
() => {};
}
#[macro_use]
.#[macro_use]
mod inner {
macro_rules! a {
() => {};
}
macro_rules! b {
() => {};
}
}
a!();
b!();
#[macro_use]
.#[macro_use(lazy_static)] // Or #[macro_use] to import all macros.
extern crate lazy_static;
lazy_static!{}
Cover image by Thom Milkovic.