diff --git a/src/SUMMARY.md b/src/SUMMARY.md index aaa0edf..bd3840a 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -4,5 +4,8 @@ - [Macros 1.0: macro_rules!](./macros_1.0.md) - [Metavariables](./macros_1.0/metavariables.md) -- [Macros 2.0]() + - [Repetitions](./macros_1.0/repetitions.md) + - [Scoping](./macros_1.0/scoping.md) + - [Hygiene](./macros_1.0/hygiene.md) +- [Macros 2.0](./macros_2.0.md) - [Procedural macros](./procedural_macros.md) diff --git a/src/introduction.md b/src/introduction.md index 980af3e..8cc33ad 100644 --- a/src/introduction.md +++ b/src/introduction.md @@ -35,10 +35,10 @@ Rust has two main kinds of macros: ## What You'll Learn -In this tutorial, you will learn: +Here's what you'll learn in this tutorial: - How to write your own macros - Best practices for macro safety and maintainability -- Common macro patterns used in the Rust ecosystem +- Common macro patterns in the Rust ecosystem ## How to Use This Book diff --git a/src/macros_1.0/hygiene.md b/src/macros_1.0/hygiene.md new file mode 100644 index 0000000..cf5d956 --- /dev/null +++ b/src/macros_1.0/hygiene.md @@ -0,0 +1,26 @@ +# Hygiene + +```rust,editable + +fn func() { + println!("Hello, world! (from definition site)"); +} + +fn main(){ + let x = 1; + macro_rules! m { + () => { + println!("x = {}", x); // Uses `x` from the definition site. + func(); // Uses `func` from the invocation site. + $crate::func(); // meta-variable `$crate` refers to the crate where the macro is defined. + }; + } + { + let x = 2; + fn func() { + println!("Hello, Rustacean! (from invocation site)"); + } + m!(); + } +} +``` diff --git a/src/macros_1.0/repetitions.md b/src/macros_1.0/repetitions.md new file mode 100644 index 0000000..dd8d6a6 --- /dev/null +++ b/src/macros_1.0/repetitions.md @@ -0,0 +1,71 @@ +# Repetitions + +We use `$()[delimiter]<*|?|+>` to specify metavariable repetition in both the pattern (matcher) and the expansion (transcriber). + +- `*`: Zero or more times +- `?`: Zero or one time +- `+`: One or more times + +## Optional Repetitions (?) + +```rust,editable +macro_rules! greet{ + ($($msg:literal)?) => { + let msg = concat!("Hello, world! ", $($msg)?); + println!("{}", msg); + } +} + +fn main(){ + greet!(); + greet!("Hello, Rustacean! 👋"); +} +``` + +## Zero or More Repetitions (*) + +```rust,editable +macro_rules! sum{ + ($($n:expr)*) => { + let mut sum=0; + $(sum += $n;)* + println!("The sum is {}", sum); + } +} +fn main(){ + sum!(1 2 3); +} +``` + +## Repetitions with Delimiters + +```rust,editable +macro_rules! sum{ + ($($n:expr),*) => { + let mut sum=0; + $(sum += $n;)* + println!("The sum is {}", sum); + } +} +fn main(){ + sum!(1, 2, 3); +} +``` + +## Custom Delimiter Tokens + +```rust,editable +macro_rules! sum{ + ($($n:literal)add*) => { + let mut sum=0; + $(sum += $n;)* + println!("The sum is {}", sum); + } +} +fn main(){ + sum!(1 add 2 add 3); +} +``` + +> [!TIP] +> The delimiter token can be any token other than a delimiter or one of the repetition operators, but `;` and `,` are the most common. diff --git a/src/macros_1.0/scoping.md b/src/macros_1.0/scoping.md new file mode 100644 index 0000000..23e7c33 --- /dev/null +++ b/src/macros_1.0/scoping.md @@ -0,0 +1,81 @@ +# Scoping + +## Textual scope + +```rust,editable +fn main(){ + // m!{}; // Error: macro `m` is not defined in this scope + + // new scope + { + macro_rules! m{ + () => { + println!("Hello, world!"); + } + } + m!{}; // OK + + // shadowing + macro_rules! m{ + () => { + println!("Hello, Rustacean!"); + } + } + m!{}; // OK + } + + // m!{}; // Error: macro `m` is not defined in this scope +} +``` + +> [!TIP] +> Textual scope works similarly to the scope of local variables declared with `let`. + +## Path-based Scope + +```rust,editable +mod mod1{ + macro_rules! m{ + () => { + println!("Hello, world!"); + } + } +} + +mod mod2{ + macro_rules! m{ + () => { + println!("Hello, Rustacean!"); + } + } + pub(crate) use m; // re-export to gain path-based scope +} + +fn main(){ + // mod1::m!{}; // Error: By default, a macro has no path-based scope. + mod2::m!{}; // OK: `m` is re-exported from `mod2`. +} +``` + +## Exporting Macros + +```rust,editable +mod mod_level_1{ + mod mod_level_2{ + // By default, a macro is implicitly `pub(crate)` + // `#[macro_export]` makes it `pub` and export it to the root of the crate. + #[macro_export] + macro_rules! m{ + () => { + println!("Hello, world!"); + } + } + } +} + +fn main(){ + // mod_level_1::m!{}; // Error: `m` is not in scope. + // mod_level_1::mod_level_2::m!{}; // Error: `m` is not in scope. + crate::m!{}; // OK +} +``` diff --git a/src/macros_2.0.md b/src/macros_2.0.md new file mode 100644 index 0000000..4076b46 --- /dev/null +++ b/src/macros_2.0.md @@ -0,0 +1,22 @@ +# Macros 2.0 + +[#39412](https://github.com/rust-lang/rust/issues/39412) + +> [!NOTE] +> Macros 2.0 is a proposal to improve the macros 1.0 (declarative macros `macro_rules!`). It is not yet implemented. + +## Why Macros 2.0? + +Macros 1.0 has several limitations: + +- Lack of hygiene: Macros 1.0 can capture variables from the surrounding scope, which can lead to unexpected behavior. +- Lack of modularity: Macros 1.0 cannot be easily composed or reused. +- Lack of type safety: Macros 1.0 cannot be easily checked for type safety. + +Macros 2.0 aims to address these limitations by providing a more powerful and flexible macro system. + +> [!NOTE] +> We will get it back when it is implemented. + +> [!TIP] +> We actually don't need macros 1.0 or macros 2.0, if we can use procedural macros.