mirror of https://github.com/rust-lang/book
Ship new content to nostarch
This commit is contained in:
parent
56a1727b66
commit
93290a3613
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2016 The Rust Project Developers. See the COPYRIGHT
|
||||
# file at the top-level directory of this distribution and at
|
||||
# http://rust-lang.org/COPYRIGHT.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
# option. This file may not be copied, modified, or distributed
|
||||
# except according to those terms.
|
||||
|
||||
set -eu
|
||||
|
||||
cargo build --release
|
||||
|
||||
mkdir -p tmp
|
||||
rm -rf tmp/*.md
|
||||
|
||||
# Get all the markdown files in the src dir,
|
||||
ls src/${1:-""}*.md | \
|
||||
# except for SUMMARY.md.
|
||||
grep -v SUMMARY.md | \
|
||||
# Extract just the filename so we can reuse it easily.
|
||||
xargs -n 1 basename | \
|
||||
# Remove all links followed by <!-- ignore -->, then
|
||||
# Change all remaining links from markdown to italicized inline text.
|
||||
while IFS= read -r filename; do
|
||||
< "src/$filename" ./target/release/remove_links \
|
||||
| ./target/release/link2print \
|
||||
| ./target/release/remove_markup > "tmp/$filename"
|
||||
done
|
||||
# Concat the files into the nostarch dir.
|
||||
./target/release/concat_chapters tmp nostarch
|
|
@ -0,0 +1,245 @@
|
|||
# Appendix D - Useful Development Tools
|
||||
|
||||
In this appendix, we’ll talk about tools provided by the Rust project that are
|
||||
useful when developing Rust code.
|
||||
|
||||
## Automatic Formatting with `rustfmt`
|
||||
|
||||
The tool `rustfmt` reformats your code according to the community code style.
|
||||
Many projects use `rustfmt` to prevent arguments about which style to use when
|
||||
writing Rust: everyone formats their code with the tool!
|
||||
|
||||
The `rustfmt` tool is not yet at the quality of a version 1.0 release, but
|
||||
a preview is available for you to use in the meantime. Please give it a try and
|
||||
let us know how it goes!
|
||||
|
||||
To install `rustfmt`:
|
||||
|
||||
```
|
||||
$ rustup component add rustfmt-preview
|
||||
```
|
||||
|
||||
This will give you both `rustfmt` and `cargo-fmt`, similar to how Rust gives
|
||||
you both `rustc` and `cargo`. To take any Cargo project and format it:
|
||||
|
||||
```
|
||||
$ cargo fmt
|
||||
```
|
||||
|
||||
Running this command will reformat all of the Rust code in the current crate.
|
||||
This should only change the code style, not the code semantics. For more
|
||||
information on `rustfmt`, see its documentation at
|
||||
*https://github.com/rust-lang-nursery/rustfmt*.
|
||||
|
||||
## Fix Up Your Code with `rustfix`
|
||||
|
||||
If you’ve written code in Rust, you’ve probably seen compiler warnings. For
|
||||
example, consider this code:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```
|
||||
fn do_something() {}
|
||||
|
||||
fn main() {
|
||||
for i in 0..100 {
|
||||
do_something();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here, we’re calling the `do_something` function 100 times, but we never use the
|
||||
variable `i` in the body of the `for` loop. Rust warns us about that:
|
||||
|
||||
```
|
||||
$ cargo build
|
||||
Compiling myprogram v0.1.0 (file:///projects/myprogram)
|
||||
warning: unused variable: `i`
|
||||
--> src/main.rs:4:9
|
||||
|
|
||||
4 | for i in 1..100 {
|
||||
| ^ help: consider using `_i` instead
|
||||
|
|
||||
= note: #[warn(unused_variables)] on by default
|
||||
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.50s
|
||||
```
|
||||
|
||||
The warning suggests that we use `_i` as a name instead: the underscore
|
||||
indicates that we intend for this variable to be unused. We can automatically
|
||||
apply that suggestion using the `rustfix` tool by running the command `cargo
|
||||
fix`:
|
||||
|
||||
```
|
||||
$ cargo fix
|
||||
Checking myprogram v0.1.0 (file:///projects/myprogram)
|
||||
Fixing src/main.rs (1 fix)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.59s
|
||||
```
|
||||
|
||||
If we look at *src/main.rs* again, we’ll see that `cargo fix` has changed the
|
||||
code:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```
|
||||
fn do_something() {}
|
||||
|
||||
fn main() {
|
||||
for _i in 0..100 {
|
||||
do_something();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `for` loop variable is now named `_i`, and the warning will no longer
|
||||
appear.
|
||||
|
||||
The `cargo fix` command can also be used to transition your code between
|
||||
different editions of Rust. Editions are covered in Appendix E.
|
||||
|
||||
## More Lints with `clippy`
|
||||
|
||||
The `clippy` tool is a collection of lints to catch common mistakes and improve
|
||||
your Rust code.
|
||||
|
||||
The `clippy` tool is not yet at the quality of a version 1.0 release, but a
|
||||
preview is available for you to use in the meantime. Please give it a try and
|
||||
let us know how it goes!
|
||||
|
||||
To install `clippy`:
|
||||
|
||||
```
|
||||
$ rustup component add clippy-preview
|
||||
```
|
||||
|
||||
To take any Cargo project and run clippy’s lints on it:
|
||||
|
||||
```
|
||||
$ cargo clippy
|
||||
```
|
||||
|
||||
For example, if you write a program that uses an approximation of a
|
||||
mathematical constant such as pi, as this program does:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```
|
||||
fn main() {
|
||||
let x = 3.1415;
|
||||
let r = 8.0;
|
||||
println!("the area of the circle is {}", x * r * r);
|
||||
}
|
||||
```
|
||||
|
||||
Running `cargo clippy` on this project will result in this error:
|
||||
|
||||
```
|
||||
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
|
||||
--> src/main.rs:2:13
|
||||
|
|
||||
2 | let x = 3.1415;
|
||||
| ^^^^^^
|
||||
|
|
||||
= note: #[deny(clippy::approx_constant)] on by default
|
||||
= help: for further information visit https://rust-lang-nursery.github.io/rust-clippy/v0.0.212/index.html#approx_constant
|
||||
```
|
||||
|
||||
This lets you know that Rust has this constant defined more precisely, and that
|
||||
your program would be more correct if you used the constant instead. This code
|
||||
doesn’t result in any errors or warnings from `clippy`:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```
|
||||
fn main() {
|
||||
let x = std::f64::consts::PI;
|
||||
let r = 8.0;
|
||||
println!("the area of the circle is {}", x * r * r);
|
||||
}
|
||||
```
|
||||
|
||||
For more information on `clippy`, see its documentation at
|
||||
*https://github.com/rust-lang-nursery/rust-clippy*.
|
||||
|
||||
## IDE Integration Using the Rust Language Server
|
||||
|
||||
To help IDE integration, the Rust project distributes the `rls`, which stands
|
||||
for the Rust Language Server. This tool speaks the Language Server Protocol
|
||||
described at *http://langserver.org/*, which is a specification for IDEs and
|
||||
programming languages to communicate with each other. The `rls` can be used by
|
||||
different clients, such as the Rust plugin for Visual Studio: Code at
|
||||
*https://marketplace.visualstudio.com/items?itemName=rust-lang.rust*.
|
||||
|
||||
The `rls` is not yet at the quality of a version 1.0 release, but a preview is
|
||||
available for you to use in the meantime. Please give it a try and let us know
|
||||
how it goes!
|
||||
|
||||
To install the `rls`:
|
||||
|
||||
```
|
||||
$ rustup component add rls-preview
|
||||
```
|
||||
|
||||
Then install the language server support in your particular IDE, and you will
|
||||
gain abilities such as autocompletion, jump to definition, and inline errors.
|
||||
|
||||
For more information on the `rls`, see its documentation at
|
||||
*https://github.com/rust-lang-nursery/rls*.
|
||||
|
||||
# Appendix E - Editions
|
||||
|
||||
Way back in Chapter 1, we saw that `cargo new` adds a bit of metadata to your
|
||||
*Cargo.toml* about an `edition`. This appendix talks about what that means!
|
||||
|
||||
The Rust language and compiler have a six-week release cycle. This means users
|
||||
get a constant stream of new features. Other programming languages release
|
||||
larger changes less often; Rust chooses to release smaller updates more
|
||||
frequently. After a while, all of those tiny changes add up. But from release
|
||||
to release, it can be hard to look back and say “Wow, between Rust 1.10 and
|
||||
Rust 1.31, Rust has changed a lot!”
|
||||
|
||||
Every two or three years, the Rust team produces a new *edition* of Rust.
|
||||
Each edition brings together the features that have landed into a clear
|
||||
package with fully updated documentation and tooling. New editions ship
|
||||
as part of the usual six-week release process.
|
||||
|
||||
This serves different purposes for different people:
|
||||
|
||||
* For active Rust users, it brings together incremental changes into an
|
||||
easy-to-understand package.
|
||||
* For non-users, it signals that some major advancements have landed, which
|
||||
might make Rust worth another look.
|
||||
* For those developing Rust itself, it provides a rallying point for the
|
||||
project as a whole.
|
||||
|
||||
At the time of writing, there are two editions: Rust 2015 and Rust 2018.
|
||||
This book is written using Rust 2018 edition idioms.
|
||||
|
||||
The `edition` key in *Cargo.toml* indicates which edition your code should be
|
||||
compiled under. If the key does not exist, it defaults to `2015` for backwards
|
||||
compatibility reasons.
|
||||
|
||||
Each project can choose to opt in to an edition other than the default 2015
|
||||
edition. By doing so, editions can contain incompatible changes, such as adding
|
||||
a new keyword that might conflict with identifiers in code or turning warnings
|
||||
into errors. But unless you opt in to those changes, your code will continue to
|
||||
compile even as you upgrade the version of the Rust compiler that you use. All
|
||||
Rust compiler versions support any edition that existed prior to that
|
||||
compiler’s release, and they can link crates of any supported editions
|
||||
together. Edition changes only affect the way the compiler initially parses
|
||||
code. Therefore, if you’re using Rust 2015 and one of your dependencies uses
|
||||
Rust 2018, your project will compile and be able to use that dependency. The
|
||||
opposite situation, where your project uses Rust 2018 and a dependency uses
|
||||
Rust 2015, works as well.
|
||||
|
||||
To be clear: most features will be available on all editions. Developers using
|
||||
any edition of Rust will continue to see improvements as new stable releases
|
||||
are made. In some cases, however, mainly when new keywords are added, there may
|
||||
be new features that are only available in later editions. You only need to
|
||||
switch editions if you want to take advantage of such features.
|
||||
|
||||
For more details, the Edition
|
||||
Guide at *https://rust-lang-nursery.github.io/edition-guide/* is a complete
|
||||
book about editions, including how to automatically upgrade your code to
|
||||
a new edition via `cargo fix`.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,595 @@
|
|||
<!-- This paragraph is the same as the one on page 448; I'm including it here
|
||||
to show where the new content should go. /Carol -->
|
||||
|
||||
This code will compile just fine. For more about trait objects, refer to the
|
||||
“Using Trait Objects That Allow for Values of Different Types” section in
|
||||
Chapter 17.
|
||||
|
||||
<!-- This is the start of the new content on macros, some of which used to be
|
||||
in Appendix D. /Carol -->
|
||||
|
||||
Next, let’s look at macros!
|
||||
|
||||
## Macros
|
||||
|
||||
<!-- This intro is new. /Carol -->
|
||||
|
||||
We’ve used macros like `println!` throughout this book, but we haven’t fully
|
||||
explored what a macro is and how it works. *Macros* refers to a family of
|
||||
features in Rust:
|
||||
|
||||
* *Declarative* macros with `macro_rules!`
|
||||
* *Procedural* macros, which come in three kinds:
|
||||
* Custom `#[derive]` macros
|
||||
* Attribute-like macros
|
||||
* Function-like macros
|
||||
|
||||
We’ll talk about each of these in turn, but first, why do we even need macros
|
||||
when we already have functions?
|
||||
|
||||
### The Difference Between Macros and Functions
|
||||
|
||||
<!-- This section is largely the same as it appears in Appendix D; I can
|
||||
provide a list of the small changes if that would be helpful. /Carol -->
|
||||
|
||||
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. We’ve
|
||||
also used the `println!` and `vec!` macros throughout the book. All of these
|
||||
macros *expand* to produce more code than the code you’ve 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. However, 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, on the other hand, can take a variable number of
|
||||
parameters: we can call `println!("hello")` with one argument or
|
||||
`println!("hello {}", name)` with two arguments. Also, macros are expanded
|
||||
before the compiler interprets the meaning of the code, so a macro can, for
|
||||
example, implement a trait on a given type. A function can’t, 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
|
||||
definitions are more complex than function definitions because you’re writing
|
||||
Rust code that writes Rust code. Due to this indirection, macro definitions are
|
||||
generally more difficult to read, understand, and maintain than function
|
||||
definitions.
|
||||
|
||||
There is one last important difference between macros and functions: you must
|
||||
define or bring macros into scope *before* you call them in a file, whereas you
|
||||
can define functions anywhere and call them anywhere.
|
||||
|
||||
### Declarative Macros with `macro_rules!` for General Metaprogramming
|
||||
|
||||
<!-- This section is largely the same as it appears in Appendix D; I can
|
||||
provide a list of the small changes if that would be helpful. /Carol -->
|
||||
|
||||
The most widely used form of macros in Rust are *declarative macros*. These are
|
||||
also sometimes 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. As discussed in Chapter 6,
|
||||
`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 have
|
||||
code associated with them; 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 is the code that
|
||||
replaces the code passed to the macro. This all happens during compilation.
|
||||
|
||||
To define a macro, you use the `macro_rules!` construct. Let’s explore how to
|
||||
use `macro_rules!` by looking at how the `vec!` macro is defined. Chapter 8
|
||||
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 with three
|
||||
integers inside:
|
||||
|
||||
```
|
||||
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 of values up front.
|
||||
|
||||
Let’s look at a slightly simplified definition of the `vec!` macro in Listing
|
||||
19-36.
|
||||
|
||||
Filename: src/lib.rs
|
||||
|
||||
```
|
||||
#[macro_export]
|
||||
macro_rules! vec {
|
||||
( $( $x:expr ),* ) => {
|
||||
{
|
||||
let mut temp_vec = Vec::new();
|
||||
$(
|
||||
temp_vec.push($x);
|
||||
)*
|
||||
temp_vec
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Listing 19-36: A simplified version of the `vec!` macro definition
|
||||
|
||||
> Note: The actual definition of the `vec!` macro in the standard 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 we’re defining the macro is brought into
|
||||
scope. Without this annotation, the macro can’t be brought into scope.
|
||||
|
||||
We then start the macro definition with `macro_rules!` and the name of the
|
||||
macro we’re defining *without* the exclamation mark. The name, in this case
|
||||
`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 that this
|
||||
is the only pattern in this macro, there is only one valid way to match; any
|
||||
other will be 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 Chapter 18 because macro patterns are matched against Rust code
|
||||
structure rather than values. Let’s walk through what the pieces of the pattern
|
||||
in Listing D-1 mean; for the full macro pattern syntax, see the reference at
|
||||
*https://doc.rust-lang.org/stable/reference/macros.html*.
|
||||
|
||||
First, a set of parentheses encompasses the whole pattern. Next comes a dollar
|
||||
sign (`$`) followed by a set of parentheses, which captures values that match
|
||||
the pattern within the parentheses for use in the replacement code. Within
|
||||
`$()` is `$x:expr`, which matches any Rust expression and gives the expression
|
||||
the name `$x`.
|
||||
|
||||
The comma following `$()` indicates that a literal comma separator character
|
||||
could optionally appear after the code that matches the code captured in `$()`.
|
||||
The `*` following the comma 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 let’s look at the pattern in the body of the code associated with this arm:
|
||||
the `temp_vec.push()` code within the `$()*` part 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 the following:
|
||||
|
||||
```
|
||||
let mut temp_vec = Vec::new();
|
||||
temp_vec.push(1);
|
||||
temp_vec.push(2);
|
||||
temp_vec.push(3);
|
||||
temp_vec
|
||||
```
|
||||
|
||||
We’ve defined a macro that can take any number of arguments of any type and can
|
||||
generate code to create a vector containing the specified elements.
|
||||
|
||||
There are some strange corners with `macro_rules!`. In the future, there
|
||||
will be a second kind of declarative macro with the `macro` keyword that
|
||||
will work in a similar fashion but fix some of these edge cases. After that
|
||||
is done, `macro_rules!` will be effectively deprecated. With this
|
||||
in mind, as well as the fact that most Rust programmers will *use* macros
|
||||
more than *write* macros, we won’t discuss `macro_rules!` any further. To
|
||||
learn more about how to write macros, consult the online documentation or
|
||||
other resources, such as “The Little Book of Rust Macros” at
|
||||
*https://danielkeep.github.io/tlborm/book/index.html*.
|
||||
|
||||
### Procedural Macros for Generating Code from Attributes
|
||||
|
||||
<!-- This section is mostly different from what's in Appendix D. /Carol -->
|
||||
|
||||
The second form of macros is called *procedural macros* because they’re more
|
||||
like functions (which are a type of procedure). Procedural macros accept some
|
||||
Rust code as an input, operate on that code, and produce some Rust code as an
|
||||
output rather than matching against patterns and replacing the code with other
|
||||
code as declarative macros do.
|
||||
|
||||
There are three kinds of procedural macros, but they all work in a similar
|
||||
fashion. First, the definitions must reside in their own crate with a special
|
||||
crate type. This is for complex technical reasons that we hope to eliminate in
|
||||
the future.
|
||||
|
||||
Second, using any of these kinds of macros takes on a form like the code shown
|
||||
in Listing 19-37, where `some_attribute` is a placeholder for using a specific
|
||||
macro.
|
||||
|
||||
Filename: src/lib.rs
|
||||
|
||||
```
|
||||
use proc_macro;
|
||||
|
||||
#[some_attribute]
|
||||
pub fn some_name(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
```
|
||||
|
||||
Listing 19-37: An example of using a procedural
|
||||
macro
|
||||
|
||||
Procedural macros consist of a function, which is how they get their name:
|
||||
“procedure” is a synonym for “function.” Why not call them “functional macros”?
|
||||
Well, one of the types is “function-like,” and that would get confusing.
|
||||
Anyway, the function defining a procedural macro takes a `TokenStream` as an
|
||||
input and produces a `TokenStream` as an output. This is the core of the macro:
|
||||
the source code that the macro is operating on makes up the input
|
||||
`TokenStream`, and the code the macro produces is the output `TokenStream`.
|
||||
Finally, the function has an attribute on it; this attribute says which kind of
|
||||
procedural macro we’re creating. We can have multiple kinds of procedural
|
||||
macros in the same crate.
|
||||
|
||||
Given that the kinds of macros are so similar, we’ll start with a custom derive
|
||||
macro. Then we’ll explain the small differences that make the other forms
|
||||
different.
|
||||
|
||||
### How to Write a Custom `derive` Macro
|
||||
|
||||
<!-- This section is largely the same as the "Procedural Macros for Custom
|
||||
derive" section in Appendix D; I can provide a list of the small changes if
|
||||
that would be helpful. /Carol -->
|
||||
|
||||
Let’s create a crate named `hello_macro` that defines a trait named
|
||||
`HelloMacro` with one associated function named `hello_macro`. Rather than
|
||||
making our crate users implement the `HelloMacro` trait for each of their
|
||||
types, we’ll provide a procedural macro so users can annotate their type with
|
||||
`#[derive(HelloMacro)]` to get a default implementation of the `hello_macro`
|
||||
function. The default implementation will print `Hello, Macro! My name is
|
||||
TypeName!` where `TypeName` is the name of the type on which this trait has
|
||||
been defined. In other words, we’ll write a crate that enables another
|
||||
programmer to write code like Listing 19-38 using our crate.
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```
|
||||
use hello_macro::HelloMacro;
|
||||
use hello_macro_derive::HelloMacro;
|
||||
|
||||
#[derive(HelloMacro)]
|
||||
struct Pancakes;
|
||||
|
||||
fn main() {
|
||||
Pancakes::hello_macro();
|
||||
}
|
||||
```
|
||||
|
||||
Listing 19-38: The code a user of our crate will be able to write when using
|
||||
our procedural macro
|
||||
|
||||
This code will print `Hello, Macro! My name is Pancakes!` when we’re done. The
|
||||
first step is to make a new library crate, like this:
|
||||
|
||||
```
|
||||
$ cargo new hello_macro --lib
|
||||
```
|
||||
|
||||
Next, we’ll define the `HelloMacro` trait and its associated function:
|
||||
|
||||
Filename: src/lib.rs
|
||||
|
||||
```
|
||||
pub trait HelloMacro {
|
||||
fn hello_macro();
|
||||
}
|
||||
```
|
||||
|
||||
We have a trait and its function. At this point, our crate user could implement
|
||||
the trait to achieve the desired functionality, like so:
|
||||
|
||||
```
|
||||
use hello_macro::HelloMacro;
|
||||
|
||||
struct Pancakes;
|
||||
|
||||
impl HelloMacro for Pancakes {
|
||||
fn hello_macro() {
|
||||
println!("Hello, Macro! My name is Pancakes!");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Pancakes::hello_macro();
|
||||
}
|
||||
```
|
||||
|
||||
However, they would need to write the implementation block for each type they
|
||||
wanted to use with `hello_macro`; we want to spare them from having to do this
|
||||
work.
|
||||
|
||||
Additionally, we can’t yet provide a default implementation for the
|
||||
`hello_macro` function that will print the name of the type the trait is
|
||||
implemented on: Rust doesn’t have reflection capabilities, so it can’t look up
|
||||
the type’s name at runtime. We need a macro to generate code at compile time.
|
||||
|
||||
The next step is to define the procedural macro. At the time of this writing,
|
||||
procedural macros need to be in their own crate. Eventually, this restriction
|
||||
might be lifted. The convention for structuring crates and macro crates is as
|
||||
follows: for a crate named `foo`, a custom derive procedural macro crate is
|
||||
called `foo_derive`. Let’s start a new crate called `hello_macro_derive` inside
|
||||
our `hello_macro` project:
|
||||
|
||||
```
|
||||
$ cargo new hello_macro_derive --lib
|
||||
```
|
||||
|
||||
Our two crates are tightly related, so we create the procedural macro crate
|
||||
within the directory of our `hello_macro` crate. If we change the trait
|
||||
definition in `hello_macro`, we’ll have to change the implementation of the
|
||||
procedural macro in `hello_macro_derive` as well. The two crates will need to
|
||||
be published separately, and programmers using these crates will need to add
|
||||
both as dependencies and bring them both into scope. We could instead have the
|
||||
`hello_macro` crate use `hello_macro_derive` as a dependency and reexport the
|
||||
procedural macro code. But the way we’ve structured the project makes it
|
||||
possible for programmers to use `hello_macro` even if they don’t want the
|
||||
`derive` functionality.
|
||||
|
||||
We need to declare the `hello_macro_derive` crate as a procedural macro crate.
|
||||
We’ll also need functionality from the `syn` and `quote` crates, as you’ll see
|
||||
in a moment, so we need to add them as dependencies. Add the following to the
|
||||
*Cargo.toml* file for `hello_macro_derive`:
|
||||
|
||||
Filename: hello_macro_derive/Cargo.toml
|
||||
|
||||
```
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "0.14.4"
|
||||
quote = "0.6.3"
|
||||
```
|
||||
|
||||
To start defining the procedural macro, place the code in Listing 19-39 into
|
||||
your *src/lib.rs* file for the `hello_macro_derive` crate. Note that this code
|
||||
won’t compile until we add a definition for the `impl_hello_macro` function.
|
||||
|
||||
Filename: hello_macro_derive/src/lib.rs
|
||||
|
||||
```
|
||||
extern crate proc_macro;
|
||||
|
||||
use crate::proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn;
|
||||
|
||||
#[proc_macro_derive(HelloMacro)]
|
||||
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
|
||||
// Construct a representation of Rust code as a syntax tree
|
||||
// that we can manipulate
|
||||
let ast = syn::parse(input).unwrap();
|
||||
|
||||
// Build the trait implementation
|
||||
impl_hello_macro(&ast)
|
||||
}
|
||||
```
|
||||
|
||||
Listing 19-39: Code that most procedural macro crates will need to have for
|
||||
processing Rust code
|
||||
|
||||
Notice the way we’ve split the functions in Listing 19-39; this will be the
|
||||
same for almost every procedural macro crate you see or create, because it
|
||||
makes writing a procedural macro more convenient. What you choose to do in the
|
||||
place where the `impl_hello_macro` function is called will be different
|
||||
depending on your procedural macro’s purpose.
|
||||
|
||||
We’ve introduced three new crates: `proc_macro`, `syn` (available from
|
||||
*https://crates.io/crates/syn*), and `quote` (available from
|
||||
*https://crates.io/crates/quote*). The `proc_macro` crate comes with Rust, so
|
||||
we didn’t need to add that to the dependencies in *Cargo.toml*. The
|
||||
`proc_macro` crate is the compiler’s API to be able to read and manipulate Rust
|
||||
code from our code. The `syn` crate parses Rust code from a string into a data
|
||||
structure that we can perform operations on. The `quote` crate takes `syn` data
|
||||
structures and turns them back into Rust code. These crates make it much
|
||||
simpler to parse any sort of Rust code we might want to handle: writing a full
|
||||
parser for Rust code is no simple task.
|
||||
|
||||
The `hello_macro_derive` function will get called when a user of our library
|
||||
specifies `#[derive(HelloMacro)]` on a type. The reason is that we’ve annotated
|
||||
the `hello_macro_derive` function here with `proc_macro_derive` and specified
|
||||
the name, `HelloMacro`, which matches our trait name; that’s the convention
|
||||
most procedural macros follow.
|
||||
|
||||
This function first converts the `input` from a `TokenStream` to a data
|
||||
structure that we can then interpret and perform operations on. This is where
|
||||
`syn` comes into play. The `parse` function in `syn` takes a `TokenStream` and
|
||||
returns a `DeriveInput` struct representing the parsed Rust code. Listing 19-40
|
||||
shows the relevant parts of the `DeriveInput` struct we get from parsing the
|
||||
string `struct Pancakes;`:
|
||||
|
||||
```
|
||||
DeriveInput {
|
||||
// --snip--
|
||||
|
||||
ident: Ident {
|
||||
ident: "Pancakes",
|
||||
span: #0 bytes(95..103)
|
||||
},
|
||||
data: Struct(
|
||||
DataStruct {
|
||||
struct_token: Struct,
|
||||
fields: Unit,
|
||||
semi_token: Some(
|
||||
Semi
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Listing 19-40: The `DeriveInput` instance we get when parsing the code that has
|
||||
the macro’s attribute in Listing 19-38
|
||||
|
||||
The fields of this struct show that the Rust code we’ve parsed is a unit struct
|
||||
with the `ident` (identifier, meaning the name) of `Pancakes`. There are more
|
||||
fields on this struct for describing all sorts of Rust code; check the `syn`
|
||||
documentation for `DeriveInput` at
|
||||
*https://docs.rs/syn/0.14.4/syn/struct.DeriveInput.html* for more information.
|
||||
|
||||
At this point, we haven’t defined the `impl_hello_macro` function, which is
|
||||
where we’ll build the new Rust code we want to include. But before we do, note
|
||||
that its output is also a `TokenStream`. The returned `TokenStream` is added to
|
||||
the code that our crate users write, so when they compile their crate, they’ll
|
||||
get extra functionality that we provide.
|
||||
|
||||
You might have noticed that we’re calling `unwrap` to panic if the call to the
|
||||
`syn::parse` function fails here. Panicking on errors is necessary in
|
||||
procedural macro code because `proc_macro_derive` functions must return
|
||||
`TokenStream` rather than `Result` to conform to the procedural macro API.
|
||||
We’ve chosen to simplify this example by using `unwrap`; in production code,
|
||||
you should provide more specific error messages about what went wrong by using
|
||||
`panic!` or `expect`.
|
||||
|
||||
Now that we have the code to turn the annotated Rust code from a `TokenStream`
|
||||
into a `DeriveInput` instance, let’s generate the code that implements the
|
||||
`HelloMacro` trait on the annotated type as shown in Listing 19-41.
|
||||
|
||||
Filename: hello_macro_derive/src/lib.rs
|
||||
|
||||
```
|
||||
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
let gen = quote! {
|
||||
impl HelloMacro for #name {
|
||||
fn hello_macro() {
|
||||
println!("Hello, Macro! My name is {}", stringify!(#name));
|
||||
}
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
}
|
||||
```
|
||||
|
||||
Listing 19-41: Implementing the `HelloMacro` trait using the parsed Rust code
|
||||
|
||||
We get an `Ident` struct instance containing the name (identifier) of the
|
||||
annotated type using `ast.ident`. The struct in Listing 19-40 shows that the
|
||||
`ident` we get when the `impl_hello_macro` function is run on the code in
|
||||
Listing 19-38 will have the `ident` field with a value of `"Pancakes"`. Thus,
|
||||
the `name` variable in Listing 19-41 will contain an `Ident` struct instance
|
||||
that, when printed, will be the string `"Pancakes"`, the name of the struct in
|
||||
Listing 19-38.
|
||||
|
||||
The `quote!` macro lets us write the Rust code that we want to return. The
|
||||
direct result of the `quote!` macro’s execution isn’t what’s expected by the
|
||||
compiler and needs to be converted to a `TokenStream`. We do this by calling
|
||||
the `into` method, which consumes this intermediate representation and returns
|
||||
a value of the required `TokenStream` type.
|
||||
|
||||
The `quote!` macro also provides some very cool templating mechanics; we can
|
||||
write `#name`, and `quote!` will replace it with the value in the variable
|
||||
named `name`. You can even do some repetition similar to the way regular macros
|
||||
work. Check out the `quote` crate’s docs at *https://docs.rs/quote* for a
|
||||
thorough introduction.
|
||||
|
||||
We want our procedural macro to generate an implementation of our `HelloMacro`
|
||||
trait for the type the user annotated, which we can get by using `#name`. The
|
||||
trait implementation has one function, `hello_macro`, whose body contains the
|
||||
functionality we want to provide: printing `Hello, Macro! My name is` and then
|
||||
the name of the annotated type.
|
||||
|
||||
The `stringify!` macro used here is built into Rust. It takes a Rust
|
||||
expression, such as `1 + 2`, and at compile time turns the expression into a
|
||||
string literal, such as `"1 + 2"`. This is different than `format!` or
|
||||
`println!`, which evaluate the expression and then turn the result into a
|
||||
`String`. There is a possibility that the `#name` input might be an expression
|
||||
to print literally, so we use `stringify!`. Using `stringify!` also saves an
|
||||
allocation by converting `#name` to a string literal at compile time.
|
||||
|
||||
At this point, `cargo build` should complete successfully in both `hello_macro`
|
||||
and `hello_macro_derive`. Let’s hook up these crates to the code in Listing
|
||||
19-38 to see the procedural macro in action! Create a new binary project in
|
||||
your *projects* directory using `cargo new pancakes`. We need to add
|
||||
`hello_macro` and `hello_macro_derive` as dependencies in the `pancakes`
|
||||
crate’s *Cargo.toml*. If you’re publishing your versions of `hello_macro` and
|
||||
`hello_macro_derive` to *https://crates.io/*, they would be regular
|
||||
dependencies; if not, you can specify them as `path` dependencies as follows:
|
||||
|
||||
```
|
||||
[dependencies]
|
||||
hello_macro = { path = "../hello_macro" }
|
||||
hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }
|
||||
```
|
||||
|
||||
Put the code from Listing 19-38 into *src/main.rs*, and run `cargo run`: it
|
||||
should print `Hello, Macro! My name is Pancakes!` The implementation of the
|
||||
`HelloMacro` trait from the procedural macro was included without the
|
||||
`pancakes` crate needing to implement it; the `#[derive(HelloMacro)]` added the
|
||||
trait implementation.
|
||||
|
||||
Next, let’s explore how the other kinds of procedural macros differ from custom
|
||||
derive macros.
|
||||
|
||||
### Attribute-like macros
|
||||
|
||||
<!-- This section is new. /Carol -->
|
||||
|
||||
Attribute-like macros are similar to custom derive macros, but instead of
|
||||
generating code for the `derive` attribute, they allow you to create new
|
||||
attributes. They’re also more flexible; `derive` only works for structs and
|
||||
enums; attributes can go on other items as well, like functions. As an example
|
||||
of using an attribute-like macro, you might have an attribute named `route`
|
||||
that annotates functions when using a web application framework:
|
||||
|
||||
```
|
||||
#[route(GET, "/")]
|
||||
fn index() {
|
||||
```
|
||||
|
||||
This `#[route]` attribute would be defined by the framework itself as a
|
||||
procedural macro. The macro definition function’s signature would look like
|
||||
this:
|
||||
|
||||
```
|
||||
#[proc_macro_attribute]
|
||||
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
```
|
||||
|
||||
Here, we have two parameters of type `TokenStream`; the first is for the
|
||||
contents of the attribute itself, that is, the `GET, "/"` part. The second is
|
||||
the body of the item the attribute is attached to, in this case, `fn index()
|
||||
{}` and the rest of the function’s body.
|
||||
|
||||
Other than that, attribute-like macros work the same way as custom derive
|
||||
macros: create a crate with the `proc-macro` crate type and implement a
|
||||
function that generates the code you want!
|
||||
|
||||
### Function-like macros
|
||||
|
||||
<!-- This section is new. /Carol -->
|
||||
|
||||
Finally, function-like macros define macros that look like function calls. For
|
||||
example, an `sql!` macro that might be called like so:
|
||||
|
||||
```
|
||||
let sql = sql!(SELECT * FROM posts WHERE id=1);
|
||||
```
|
||||
|
||||
This macro would parse the SQL statement inside of it and check that it’s
|
||||
syntactically correct. This macro would be defined like this:
|
||||
|
||||
```
|
||||
#[proc_macro]
|
||||
pub fn sql(input: TokenStream) -> TokenStream {
|
||||
```
|
||||
|
||||
This is similar to the custom derive macro’s signature: we get in the tokens
|
||||
that are inside of the parentheses, and return the code we wanted to generate.
|
||||
|
||||
## Summary
|
||||
|
||||
<!-- This section is the same as the existing summary on page 448 and is
|
||||
included here to show how the new content should fit in. /Carol -->
|
||||
|
||||
Whew! Now you have some features of Rust in your toolbox that you won’t use
|
||||
often, but you’ll know they’re available in very particular circumstances.
|
||||
We’ve introduced several complex topics so that when you encounter them in
|
||||
error message suggestions or in other peoples’ code, you’ll be able to
|
||||
recognize these concepts and syntax. Use this chapter as a reference to guide
|
||||
you to solutions.
|
||||
|
||||
Next, we’ll put everything we’ve discussed throughout the book into practice
|
||||
and do one more project!
|
Loading…
Reference in New Issue