started ch20.5
All checks were successful
Test Gitea Actions / first (push) Successful in 20s
Test Gitea Actions / check-code (push) Successful in 16s
Test Gitea Actions / test (push) Successful in 16s
Test Gitea Actions / documentation-check (push) Successful in 16s

This commit is contained in:
darkicewolf50 2025-04-17 12:50:58 -06:00
parent e6bb4977b8
commit 4274106509
4 changed files with 223 additions and 9 deletions

View File

@ -13,12 +13,40 @@
"state": {
"type": "markdown",
"state": {
"file": "Advanced Functions and Closures.md",
"file": "Advanced Features.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Advanced Functions and Closures"
"title": "Advanced Features"
}
},
{
"id": "0cdb5cb65c12cd29",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Macros.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Macros"
}
},
{
"id": "25c7cbda3cd74261",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Macros.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Macros"
}
},
{
@ -73,7 +101,8 @@
"title": "Graph view"
}
}
]
],
"currentTab": 1
}
],
"direction": "vertical"
@ -216,13 +245,14 @@
"command-palette:Open command palette": false
}
},
"active": "de2ac5df5b921166",
"active": "0cdb5cb65c12cd29",
"lastOpenFiles": [
"Advanced Features.md",
"Advanced Types.md",
"Advanced Traits.md",
"Enums.md",
"Advanced Functions and Closures.md",
"Advanced Traits.md",
"Macros.md",
"Advanced Types.md",
"Enums.md",
"Unsafe Rust.md",
"Pattern Matching.md",
"Places Patterns Can Be Used.md",
@ -243,7 +273,6 @@
"Sync and Send.md",
"().md",
"Smart Pointers.md",
"Simultaneous Code Running.md",
"minigrep/src/lib.rs",
"does_not_compile.svg",
"Untitled.canvas",

View File

@ -12,5 +12,5 @@ In this chapter we will cover
- [Advanced traits](./Advanced%20Traits.md): associated types, default type parameters, fully qualified syntax, supertraits, and the new type pattern in relation to traits
- [Advanced types](./Advanced%20Types.md): more about the newtype pattern, type aliases, the never type, and dynamically sized types
- [Advanced functions and closures](./Advanced%20Functions%20and%20Closures.md): function pointers and returning closures
- [Macros](): ways to define code that defines more code at compile time
- [Macros](./Macros.md): ways to define code that defines more code at compile time
a

View File

@ -104,3 +104,42 @@ Some prefer to use this style, and some people prefer to use closures.
They compile to the same code, so use whatever style is clearer to you.
## Returning Closures
Closures are represented by traits, meaning that you can't return closures directly.
In cases where you want to return a trait, you instead use the concrete type that implements the trait as the return value of the function.
You are unable to do that with closures because they do not have a concrete type that is returnable.
You are not allowed to use the function pointer `fn` as a return type, for example.
Instead, you would normally use the `impl Trait` syntax we learned from Ch10.
You can return any function type using the `Fn`, `FnOnce` and `FnMut`.
This code would work just fine.
```rust
fn returns_closure() -> impl Fn(i32) -> i32 {
|x| x + 1
}
```
As noted in the ["Closure Type Inference and Annotation"](./Closures.md#closure-type-inference-and-annoation) section in Ch13, each closure is also its own distinct type.
If you needed to work with multiple functions that have the same signature but slightly different implementations, you will need to use a trait object for them.
```rust
fn main() {
let handlers = vec![returns_closure(), returns_initialized_closure(123)];
for handler in handlers {
let output = handler(5);
println!("{output}");
}
}
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
fn returns_initialized_closure(init: i32) -> Box<dyn Fn(i32) -> i32> {
Box::new(move |x| x + init)
}
```
This will compile just fine, but it wouldn't if we had tried to stick with `impl Fn(i32) -> i32`.

146
Macros.md Normal file
View File

@ -0,0 +1,146 @@
# Macros
The term *macro* refers to a family of features in Rust: *declarative* macros with `macro_rules!` and three kinds of *procedural* macros:
- 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
Each will be discussed, but let's first look at why we even need macros when we already have functions.
## The Difference Between Macros and Functions
Fundamentally macros are a way of writing code that writes other code, which is known as *metaprogramming*.
In Appendix C, we discuss the `derive` attribute, which generates an implementation of various traits for you.
All of these macros *expand* to produce more code than the code you have written manually.
Metaprogramming is useful for reducing the amount of code you have to write and maintain, which is also one of the roles of functions.
Macros have some additional powers that functions don't have.
A function signature must declare the number and type of parameters the function has.
Macros can take a variable number of parameters.
To show this: we can call `println!("hello")` with one argument or `println!("hello {}", name)` with two arguments.
Macros are also expanded before the compiler interprets the meaning of the code, so a macro can.
For example, implement a trait on a give type.
A function cannot, because it gets called at runtime and a trait needs to be implemented at compile time.
The downside to implementing a macro instead of a function is that macro definition are more complex than function definitions because you are writing Rust code that writes Rust code.
This indirection of macro definitions are generally more difficult to read, understand and maintain than function definitions.
Another important difference between macros and functions is that you must define macros or bring them into scope *before* you call them in a file.
This is opposed to functions where you can define anywhere and call anywhere.
## Declarative Macros with `macro_rules!` for General Metaprogramming
The most widely used form of macros in Rust is the *declarative macro*.
These are sometimes also referred to as "macros by example," *"`macro rules` macros"* or just plain "macros."
At their core, declarative macros allow you to write something similar to a Rust `match` expression.
`match` expressions are control structures that take an expression, compare the resulting value of the expression to patterns, and then run the code associated with the matching pattern.
Macros also compare a value to patterns that are associated with particular code.
In this situation, the value is the literal Rust source code passed to the macro, the patterns are compared with the structure of that source code and the code associated with each pattern, when matched, replaces the code passed to the macro.
This happens during compilation.
In order to define a macro, you use the `macro_rules!` construct.
Now lets explore how to use `macro_rules!` by looking at how the `vec!` macro is defined.
Ch8 covered how we can use the `vec!` macro to create a new vector with particular values.
For example, the following macro creates a new vector containing three integers:
```rust
let v: Vec<u32> = vec![1, 2, 3];
```
We could also use the `vec!` macro to make a vector of two integers or a vector of five string slices.
We wouldn't be able to use a function to do the same because we wouldn't know the number or type values up front.
Here shows a slightly simplified definition of the `vec!` macro.
```rust
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
```
Note: The actual definition of the `vec!` macro in std library includes code to preallocate the correct amount of memory up front.
That code is an optimization that we don't include here to make the example simpler.
The `#[macro_export]` annotation indicates that this macro should be made available whenever the crate in which the macro is defined is brought into scope.
We then start the macro definition with `macro_rules!` and the name of the macro we are defining *without* the exclamation mark.
Then name here `vec` is followed by curly brackets denoting the body of the macro definition.
The structure in the `vec!` body is similar to the structure of a `match` expression.
Here we have one arm with the pattern `( $( $x:expr ),* )`, followed by `=>` and the block of code associated with this pattern.
If the pattern matches, the associated block of code will be emitted.
Given this is the only pattern in this macro, there is only one valid way to match; any other pattern will result in an error.
More complex macros will have more than one arm.
Valid pattern syntax in macro definitions is different than the pattern syntax covered in Ch19.
This is because macro patterns are matched against Rust code structure rather than values.
Now lets go over what the pattern pieces in the previous examples mean.
The full macro pattern syntax can be seen in the [Rust Reference](https://doc.rust-lang.org/reference/macros-by-example.html).
First, we use a set of parentheses to encompass the whole pattern.
We use a dollar sign (`$`) to declare a variable in the macro system that will contain the Rust code matching the pattern.
The dollar sign makes it clear this a macro variable as opposed to a regular Rust variable.
Next comes a set of parentheses that captures values that match the pattern within the parentheses for use in the replacement code.
Within `$()` is `$x: expr`, this matches any Rust expression and gives the expression the name `$x`.
The comma following `$()` indicates that a literal comma separator character must appear between each instance of the code that matches the code within `$()`.
The `*` specifies that the pattern matches zero or more of whatever precedes the `*`.
When we call this macro with `vec![1, 2, 3];`, the `$x` pattern matches three times with the three expressions `1`, `2`, and `3`.
Now lets look at the pattern in the body of the code associated with this arm: `temp_vec.push()` within `$()*` is generated for each part that matches `$()` in the pattern zero or more times depending on how many times the pattern matches.
The `$x` is replaced with each expression matched.
When we call this macro with `vec![1, 2, 3];` the code generated that replaces this macro call will be this:
```rust
{
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
}
```
Here we dined a macro that can take any number of arguments of any type and can generate code to create a vector containing the specified elements.
To learn more about how to write macros, read online documentation or other resources like ["The Little Book of Rust Macros"](https://veykril.github.io/tlborm/) started by Daniel Keep and continued by Lukas Wirth.
## Procedural Macros for Generating Code from Attributes