Skip to content

Commit 2cb7a60

Browse files
steveklabniknikomatsakis
authored andcommitted
Document custom derive.
These are some bare-bones documentation for custom derive, needed to stabilize "macros 1.1", #35900 The book chapter is based off of a blog post by @cbreeden, https://cbreeden.github.io/Macros11/ Normally, we have a policy of not mentioning external crates in documentation. However, given that syn/quote are basically neccesary for properly using macros 1.1, I feel that not including them here would make the documentation very bad. So the rules should be bent in this instance.
1 parent ce1a38c commit 2cb7a60

File tree

4 files changed

+258
-12
lines changed

4 files changed

+258
-12
lines changed

src/doc/book/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
* [Borrow and AsRef](borrow-and-asref.md)
5353
* [Release Channels](release-channels.md)
5454
* [Using Rust without the standard library](using-rust-without-the-standard-library.md)
55+
* [Procedural Macros (and custom derive)](procedural-macros.md)
5556
* [Nightly Rust](nightly-rust.md)
5657
* [Compiler Plugins](compiler-plugins.md)
5758
* [Inline Assembly](inline-assembly.md)

src/doc/book/procedural-macros.md

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
% Procedural Macros (and custom Derive)
2+
3+
As you've seen throughout the rest of the book, Rust provides a mechanism
4+
called "derive" that lets you implement traits easily. For example,
5+
6+
```rust
7+
#[derive(Debug)]
8+
struct Point {
9+
x: i32,
10+
y: i32,
11+
}
12+
```
13+
14+
is a lot simpler than
15+
16+
```rust
17+
struct Point {
18+
x: i32,
19+
y: i32,
20+
}
21+
22+
use std::fmt;
23+
24+
impl fmt::Debug for Point {
25+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26+
write!(f, "Point {{ x: {}, y: {} }}", self.x, self.y)
27+
}
28+
}
29+
```
30+
31+
Rust includes several traits that you can derive, but it also lets you define
32+
your own. We can accomplish this task through a feature of Rust called
33+
"procedural macros." Eventually, procedural macros will allow for all sorts of
34+
advanced metaprogramming in Rust, but today, they're only for custom derive.
35+
36+
Let's build a very simple trait, and derive it with custom derive.
37+
38+
## Hello World
39+
40+
So the first thing we need to do is start a new crate for our project.
41+
42+
```bash
43+
$ cargo new --bin hello-world
44+
```
45+
46+
All we want is to be able to call `hello_world()` on a derived type. Something
47+
like this:
48+
49+
```rust,ignore
50+
#[derive(HelloWorld)]
51+
struct Pancakes;
52+
53+
fn main() {
54+
Pancakes::hello_world();
55+
}
56+
```
57+
58+
With some kind of nice output, like `Hello, World! My name is Pancakes.`.
59+
60+
Let's go ahead and write up what we think our macro will look like from a user
61+
perspective. In `src/main.rs` we write:
62+
63+
```rust,ignore
64+
#[macro_use]
65+
extern crate hello_world_derive;
66+
67+
trait HelloWorld {
68+
fn hello_world();
69+
}
70+
71+
#[derive(HelloWorld)]
72+
struct FrenchToast;
73+
74+
#[derive(HelloWorld)]
75+
struct Waffles;
76+
77+
fn main() {
78+
FrenchToast::hello_world();
79+
Waffles::hello_world();
80+
}
81+
```
82+
83+
Great. So now we just need to actually write the procedural macro. At the
84+
moment, procedural macros need to be in their own crate. Eventually, this
85+
restriction may be lifted, but for now, it's required. As such, there's a
86+
convention; for a crate named `foo`, a custom derive procedural macro is called
87+
`foo-derive`. Let's start a new crate called `hello-world-derive` inside our
88+
`hello-world` project.
89+
90+
```bash
91+
$ cargo new hello-world-derive
92+
```
93+
94+
To make sure that our `hello-world` crate is able to find this new crate we've
95+
created, we'll add it to our toml:
96+
97+
```toml
98+
[dependencies]
99+
hello-world-derive = { path = "hello-world-derive" }
100+
```
101+
102+
As for our the source of our `hello-world-derive` crate, here's an example:
103+
104+
```rust,ignore
105+
extern crate proc_macro;
106+
extern crate syn;
107+
#[macro_use]
108+
extern crate quote;
109+
110+
use proc_macro::TokenStream;
111+
112+
#[proc_macro_derive(HelloWorld)]
113+
pub fn hello_world(input: TokenStream) -> TokenStream {
114+
// Construct a string representation of the type definition
115+
let s = input.to_string();
116+
117+
// Parse the string representation
118+
let ast = syn::parse_macro_input(&s).unwrap();
119+
120+
// Build the impl
121+
let gen = impl_hello_world(&ast);
122+
123+
// Return the generated impl
124+
gen.parse().unwrap()
125+
}
126+
```
127+
128+
So there is a lot going on here. We have introduced two new crates: [`syn`] and
129+
[`quote`]. As you may have noticed, `input: TokenSteam` is immediately converted
130+
to a `String`. This `String` is a string representation of the Rust code for which
131+
we are deriving `HelloWorld` for. At the moment, the only thing you can do with a
132+
`TokenStream` is convert it to a string. A richer API will exist in the future.
133+
134+
So what we really need is to be able to _parse_ Rust code into something
135+
usable. This is where `syn` comes to play. `syn` is a crate for parsing Rust
136+
code. The other crate we've introduced is `quote`. It's essentially the dual of
137+
`syn` as it will make generating Rust code really easy. We could write this
138+
stuff on our own, but it's much simpler to use these libraries. Writing a full
139+
parser for Rust code is no simple task.
140+
141+
[`syn`]: https://crates.io/crates/syn
142+
[`quote`]: https://crates.io/crates/quote
143+
144+
The comments seem to give us a pretty good idea of our overall strategy. We
145+
are going to take a `String` of the Rust code for the type we are deriving, parse
146+
it using `syn`, construct the implementation of `hello_world` (using `quote`),
147+
then pass it back to Rust compiler.
148+
149+
One last note: you'll see some `unwrap()`s there. If you want to provide an
150+
error for a procedural macro, then you should `panic!` with the error message.
151+
In this case, we're keeping it as simple as possible.
152+
153+
Great, so let's write `impl_hello_world(&ast)`.
154+
155+
```rust,ignore
156+
fn impl_hello_world(ast: &syn::MacroInput) -> quote::Tokens {
157+
let name = &ast.ident;
158+
quote! {
159+
impl HelloWorld for #name {
160+
fn hello_world() {
161+
println!("Hello, World! My name is {}", stringify!(#name));
162+
}
163+
}
164+
}
165+
}
166+
```
167+
168+
So this is where quotes comes in. The `ast` argument is a struct that gives us
169+
a representation of our type (which can be either a `struct` or an `enum`).
170+
Check out the [docs](https://docs.rs/syn/0.10.5/syn/struct.MacroInput.html),
171+
there is some useful information there. We are able to get the name of the
172+
type using `ast.ident`. The `quote!` macro let's us write up the Rust code
173+
that we wish to return and convert it into `Tokens`. `quote!` let's us use some
174+
really cool templating mechanics; we simply write `#name` and `quote!` will
175+
replace it with the variable named `name`. You can even do some repetition
176+
similar to regular macros work. You should check out the
177+
[docs](https://docs.rs/quote) for a good introduction.
178+
179+
So I think that's it. Oh, well, we do need to add dependencies for `syn` and
180+
`quote` in the `cargo.toml` for `hello-world-derive`.
181+
182+
```toml
183+
[dependencies]
184+
syn = "0.10.5"
185+
quote = "0.3.10"
186+
```
187+
188+
That should be it. Let's try to compile `hello-world`.
189+
190+
```bash
191+
error: the `#[proc_macro_derive]` attribute is only usable with crates of the `proc-macro` crate type
192+
--> hello-world-derive/src/lib.rs:8:3
193+
|
194+
8 | #[proc_macro_derive(HelloWorld)]
195+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
196+
```
197+
198+
Oh, so it appears that we need to declare that our `hello-world-derive` crate is
199+
a `proc-macro` crate type. How do we do this? Like this:
200+
201+
```toml
202+
[lib]
203+
proc-macro = true
204+
```
205+
206+
Ok so now, let's compile `hello-world`. Executing `cargo run` now yields:
207+
208+
```bash
209+
Hello, World! My name is FrenchToast
210+
Hello, World! My name is Waffles
211+
```
212+
213+
We've done it!

src/doc/reference.md

+42-8
Original file line numberDiff line numberDiff line change
@@ -555,26 +555,24 @@ mod a {
555555
# fn main() {}
556556
```
557557

558-
# Syntax extensions
558+
# Macros
559559

560560
A number of minor features of Rust are not central enough to have their own
561561
syntax, and yet are not implementable as functions. Instead, they are given
562562
names, and invoked through a consistent syntax: `some_extension!(...)`.
563563

564-
Users of `rustc` can define new syntax extensions in two ways:
565-
566-
* [Compiler plugins][plugin] can include arbitrary Rust code that
567-
manipulates syntax trees at compile time. Note that the interface
568-
for compiler plugins is considered highly unstable.
564+
Users of `rustc` can define new macros in two ways:
569565

570566
* [Macros](book/macros.html) define new syntax in a higher-level,
571567
declarative way.
568+
* [Procedural Macros][procedural macros] can be used to implement custom derive.
569+
570+
And one unstable way: [compiler plugins][plugin].
572571

573572
## Macros
574573

575574
`macro_rules` allows users to define syntax extension in a declarative way. We
576-
call such extensions "macros by example" or simply "macros" — to be distinguished
577-
from the "procedural macros" defined in [compiler plugins][plugin].
575+
call such extensions "macros by example" or simply "macros".
578576

579577
Currently, macros can expand to expressions, statements, items, or patterns.
580578

@@ -652,6 +650,28 @@ Rust syntax is restricted in two ways:
652650

653651
[RFC 550]: https://github.com/rust-lang/rfcs/blob/master/text/0550-macro-future-proofing.md
654652

653+
## Procedrual Macros
654+
655+
"Procedrual macros" are the second way to implement a macro. For now, the only
656+
thing they can be used for is to implement derive on your own types. See
657+
[the book][procedural macros] for a tutorial.
658+
659+
Procedural macros involve a few different parts of the language and its
660+
standard libraries. First is the `proc_macro` crate, included with Rust,
661+
that defines an interface for building a procedrual macro. The
662+
`#[proc_macro_derive(Foo)]` attribute is used to mark the the deriving
663+
function. This function must have the type signature:
664+
665+
```rust,ignore
666+
use proc_macro::TokenStream;
667+
668+
#[proc_macro_derive(Hello)]
669+
pub fn hello_world(input: TokenStream) -> TokenStream
670+
```
671+
672+
Finally, procedural macros must be in their own crate, with the `proc-macro`
673+
crate type.
674+
655675
# Crates and source files
656676

657677
Although Rust, like any other language, can be implemented by an interpreter as
@@ -2309,6 +2329,9 @@ impl<T: PartialEq> PartialEq for Foo<T> {
23092329
}
23102330
```
23112331

2332+
You can implement `derive` for your own type through [procedural
2333+
macros](#procedural-macros).
2334+
23122335
### Compiler Features
23132336

23142337
Certain aspects of Rust may be implemented in the compiler, but they're not
@@ -4112,6 +4135,16 @@ be ignored in favor of only building the artifacts specified by command line.
41124135
in dynamic libraries. This form of output is used to produce statically linked
41134136
executables as well as `staticlib` outputs.
41144137

4138+
* `--crate-type=proc-macro`, `#[crate_type = "proc-macro"]` - The output
4139+
produced is not specified, but if a `-L` path is provided to it then the
4140+
compiler will recognize the output artifacts as a macro and it can be loaded
4141+
for a program. If a crate is compiled with the `proc-macro` crate type it
4142+
will forbid exporting any items in the crate other than those functions
4143+
tagged `#[proc_macro_derive]` and those functions must also be placed at the
4144+
crate root. Finally, the compiler will automatically set the
4145+
`cfg(proc_macro)` annotation whenever any crate type of a compilation is the
4146+
`proc-macro` crate type.
4147+
41154148
Note that these outputs are stackable in the sense that if multiple are
41164149
specified, then the compiler will produce each form of output at once without
41174150
having to recompile. However, this only applies for outputs specified by the
@@ -4289,3 +4322,4 @@ that have since been removed):
42894322

42904323
[ffi]: book/ffi.html
42914324
[plugin]: book/compiler-plugins.html
4325+
[procedural macros]: book/procedural-macros.html

src/libproc_macro/lib.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,13 @@
1515
//! Currently the primary use of this crate is to provide the ability to define
1616
//! new custom derive modes through `#[proc_macro_derive]`.
1717
//!
18-
//! Added recently as part of [RFC 1681] this crate is stable as of Rust 1.15.0.
19-
//!
20-
//! [RFC 1681]: https://github.com/rust-lang/rfcs/blob/master/text/1681-macros-1.1.md
21-
//!
2218
//! Note that this crate is intentionally very bare-bones currently. The main
2319
//! type, `TokenStream`, only supports `fmt::Display` and `FromStr`
2420
//! implementations, indicating that it can only go to and come from a string.
2521
//! This functionality is intended to be expanded over time as more surface
2622
//! area for macro authors is stabilized.
23+
//!
24+
//! See [the book](../../book/procedural-macros.html) for more.
2725
2826
#![crate_name = "proc_macro"]
2927
#![stable(feature = "proc_macro_lib", since = "1.15.0")]

0 commit comments

Comments
 (0)