24 Rustlings Macros Solution
Rust's macro system is very powerful, but also kind of difficult to wrap your head around. We're not going to teach you how to write your own fully-featured macros. Instead, we'll show you how to use and create them.
If you'd like to learn more about writing your own macros, the macrokata project has a similar style of exercises to Rustlings, but is all about learning to write Macros.
Further information
Macros1.rs
// macros1.rs
// Execute `rustlings hint macros1` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
fn main() {
my_macro();
}
This looks like a fairly simple exercise using Macros, let's take a look at the errors.
Macros1.rs Errors
The Rust compiler tells us directly what the problem is, we're missing the !
symbol to call our macro, just like we do for println!
error[E0423]: expected function, found macro `my_macro`
--> exercises/macros/macros1.rs:13:5
|
13 | my_macro();
| ^^^^^^^^ not a function
|
help: use `!` to invoke the macro
|
13 | my_macro!();
| +
error: aborting due to previous error; 1 warning emitted
Macros1.rs Solution
Following the instructions from the compiler message and simply adding the !
to our calling of my_macro
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
fn main() {
my_macro!(); // adding `!` here
}
With that we have our code compiling and first macro exercise done. Output:
✅ Successfully ran exercises/macros/macros1.rs!
🎉 🎉 The code is compiling! 🎉 🎉
Output:
====================
Check out my macro!
====================
Macros2.rs
// macros2.rs
// Execute `rustlings hint macros2` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
fn main() {
my_macro!();
}
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
This one doesn't give us any instruction but we can see the code is not compiling. Let's look at the errors.
Macros2.rs Errors
--> exercises/macros/macros2.rs:7:5
|
7 | my_macro!();
| ^^^^^^^^
|
= help: have you added the `#[macro_use]` on the module/import?
warning: unused macro definition: `my_macro`
--> exercises/macros/macros2.rs:10:14
|
10 | macro_rules! my_macro {
| ^^^^^^^^
|
= note: `#[warn(unused_macros)]` on by default
error: aborting due to previous error; 1 warning emitted
We can see errors about not having the #[macro_use]
module imported, but I'm not sure if that's exactly what is wrong here. Let's see if there's an easy way to solve this problem below.
Macros2.rs Solution
The solution is rather simple, we have to change the placement of the macro_rules!
function an place it before the fn main()
. The reason is that macro's don't abide by the same set of rules that the rest of Rust does, in the sense that order does matter. So you have to define a macro before you can use it. So simply declaring it before calling it, or in our case copy and pasting my_macro
before fn main()
and everything should work.
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
fn main() {
my_macro!();
}
It compiles and prints out the same message as before. So we're done here.
Macros3.rs
// macros3.rs
// Make me compile, without taking the macro out of the module!
// Execute `rustlings hint macros3` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
mod macros {
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
}
fn main() {
my_macro!();
}
it looks like we have a very similar set of lines here with this exercise except that we have the mod macros
encapsulating our my_macro
definition.
Macros3.rs Errors
error: cannot find macro `my_macro` in this scope
--> exercises/macros/macros3.rs:16:5
|
16 | my_macro!();
| ^^^^^^^^
|
= help: have you added the `#[macro_use]` on the module/import?
warning: unused macro definition: `my_macro`
--> exercises/macros/macros3.rs:8:18
|
8 | macro_rules! my_macro {
| ^^^^^^^^
|
= note: `#[warn(unused_macros)]` on by default
So here we see the similar message about #[macro_use]
but this time we might have a legitimate reason to use it.
Macros3.rs Solution
So since we have the my_macro
macro wrapped in the mod macro
we nee to use the #[macro_use]
to allow use of the macro outside of the module.
// adding this line here
#[macro_use]
mod macros {
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
}
fn main() {
my_macro!();
}
Once again with this change we are compiling and printing Check out my macro!
as before.
Macros4.rs
// macros4.rs
// Execute `rustlings hint macros4` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
#[rustfmt::skip]
macro_rules! my_macro {
() => {
println!("Check out my macro!");
}
($val:expr) => {
println!("Look at this other macro: {}", $val);
}
}
fn main() {
my_macro!();
my_macro!(7777);
}
We see an expanded version of our macro that has an additional arm with that prints out a value if you give it one. But of course we're not compiling right now with how the code is constructed. So, let's look at the errors for any additional hints.
Macros4.rs Errors
⚠️ Compiling of exercises/macros/macros4.rs failed! Please try again. Here's the output:
error: no rules expected the token `(`
--> exercises/macros/macros4.rs:11:5
|
11 | ($val:expr) => {
| ^ no rules expected this token in macro call
error: aborting due to previous error
The error we're seeing is that it was expecting a token, specifically the (
but for some reason it's not seeing it. Typically when we're trying to compile our code when we see this type of error it is because we are missing something between our blocks of code. What could that be?
Macros4.rs Solution
This solution is again painfully simple all we need to do to get this to compile is properly separate the arms of the macro by adding ;
at the end of the first macro. This let's the Rust compiler know that we've ended the first arm of our macro so it then knows we have 2 different arms in the my_macro
macro.
#[rustfmt::skip]
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
($val:expr) => {
println!("Look at this other macro: {}", $val);
}
}
fn main() {
my_macro!();
my_macro!(7777);
}
with this we have our code compiling and printing:
✅ Successfully ran exercises/macros/macros4.rs!
🎉 🎉 The code is compiling! 🎉 🎉
Output:
====================
Check out my macro!
Look at this other macro: 7777
====================
Conclusion
Working with macros in Rust can initially seem daunting due to their unique syntax and behavior. However, as we've seen in these exercises, common issues often have straightforward solutions once you understand how macros operate. Remember, macros are expanded at compile time, and their scope and definition order matter. By practicing with examples and understanding the error messages, you can become more comfortable with using and creating macros in Rust. They are a powerful tool in the Rust programmer's toolkit, enabling more expressive and flexible code. Keep experimenting and exploring the Rust documentation and community resources to deepen your understanding of macros and their capabilities in Rust programming.