]> git.proxmox.com Git - rustc.git/blame - src/doc/book/first-edition/src/procedural-macros.md
New upstream version 1.19.0+dfsg1
[rustc.git] / src / doc / book / first-edition / src / procedural-macros.md
CommitLineData
8bb4bdeb 1# Procedural Macros (and custom Derive)
476ff2be
SL
2
3As you've seen throughout the rest of the book, Rust provides a mechanism
4called "derive" that lets you implement traits easily. For example,
5
6```rust
7#[derive(Debug)]
8struct Point {
9 x: i32,
10 y: i32,
11}
12```
13
14is a lot simpler than
15
16```rust
17struct Point {
18 x: i32,
19 y: i32,
20}
21
22use std::fmt;
23
24impl 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
31Rust includes several traits that you can derive, but it also lets you define
32your own. We can accomplish this task through a feature of Rust called
33"procedural macros." Eventually, procedural macros will allow for all sorts of
34advanced metaprogramming in Rust, but today, they're only for custom derive.
35
36Let's build a very simple trait, and derive it with custom derive.
37
38## Hello World
39
40So 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
46All we want is to be able to call `hello_world()` on a derived type. Something
47like this:
48
49```rust,ignore
50#[derive(HelloWorld)]
51struct Pancakes;
52
53fn main() {
54 Pancakes::hello_world();
55}
56```
57
58With some kind of nice output, like `Hello, World! My name is Pancakes.`.
59
60Let's go ahead and write up what we think our macro will look like from a user
61perspective. In `src/main.rs` we write:
62
63```rust,ignore
64#[macro_use]
65extern crate hello_world_derive;
66
67trait HelloWorld {
68 fn hello_world();
69}
70
71#[derive(HelloWorld)]
72struct FrenchToast;
73
74#[derive(HelloWorld)]
75struct Waffles;
76
77fn main() {
78 FrenchToast::hello_world();
79 Waffles::hello_world();
80}
81```
82
83Great. So now we just need to actually write the procedural macro. At the
84moment, procedural macros need to be in their own crate. Eventually, this
85restriction may be lifted, but for now, it's required. As such, there's a
86convention; 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
94To make sure that our `hello-world` crate is able to find this new crate we've
95created, we'll add it to our toml:
96
97```toml
98[dependencies]
99hello-world-derive = { path = "hello-world-derive" }
100```
101
8bb4bdeb 102As for the source of our `hello-world-derive` crate, here's an example:
476ff2be
SL
103
104```rust,ignore
105extern crate proc_macro;
106extern crate syn;
107#[macro_use]
108extern crate quote;
109
110use proc_macro::TokenStream;
111
112#[proc_macro_derive(HelloWorld)]
113pub 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
cc61c64b 118 let ast = syn::parse_derive_input(&s).unwrap();
476ff2be
SL
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
128So 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
130to a `String`. This `String` is a string representation of the Rust code for which
8bb4bdeb 131we are deriving `HelloWorld`. At the moment, the only thing you can do with a
476ff2be
SL
132`TokenStream` is convert it to a string. A richer API will exist in the future.
133
134So what we really need is to be able to _parse_ Rust code into something
135usable. This is where `syn` comes to play. `syn` is a crate for parsing Rust
136code. 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
138stuff on our own, but it's much simpler to use these libraries. Writing a full
139parser for Rust code is no simple task.
140
141[`syn`]: https://crates.io/crates/syn
142[`quote`]: https://crates.io/crates/quote
143
144The comments seem to give us a pretty good idea of our overall strategy. We
145are going to take a `String` of the Rust code for the type we are deriving, parse
146it using `syn`, construct the implementation of `hello_world` (using `quote`),
147then pass it back to Rust compiler.
148
149One last note: you'll see some `unwrap()`s there. If you want to provide an
150error for a procedural macro, then you should `panic!` with the error message.
151In this case, we're keeping it as simple as possible.
152
153Great, so let's write `impl_hello_world(&ast)`.
154
155```rust,ignore
7cac9316 156fn impl_hello_world(ast: &syn::DeriveInput) -> quote::Tokens {
476ff2be
SL
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
168So this is where quotes comes in. The `ast` argument is a struct that gives us
169a representation of our type (which can be either a `struct` or an `enum`).
7cac9316 170Check out the [docs](https://docs.rs/syn/0.11.11/syn/struct.DeriveInput.html),
476ff2be 171there is some useful information there. We are able to get the name of the
8bb4bdeb
XL
172type using `ast.ident`. The `quote!` macro lets us write up the Rust code
173that we wish to return and convert it into `Tokens`. `quote!` lets us use some
476ff2be
SL
174really cool templating mechanics; we simply write `#name` and `quote!` will
175replace it with the variable named `name`. You can even do some repetition
176similar to regular macros work. You should check out the
177[docs](https://docs.rs/quote) for a good introduction.
178
179So 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]
7cac9316
XL
184syn = "0.11.11"
185quote = "0.3.15"
476ff2be
SL
186```
187
188That should be it. Let's try to compile `hello-world`.
189
190```bash
191error: 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 |
1948 | #[proc_macro_derive(HelloWorld)]
195 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
196```
197
198Oh, so it appears that we need to declare that our `hello-world-derive` crate is
199a `proc-macro` crate type. How do we do this? Like this:
200
201```toml
202[lib]
203proc-macro = true
204```
205
206Ok so now, let's compile `hello-world`. Executing `cargo run` now yields:
207
208```bash
209Hello, World! My name is FrenchToast
210Hello, World! My name is Waffles
211```
212
213We've done it!
8bb4bdeb
XL
214
215## Custom Attributes
216
217In some cases it might make sense to allow users some kind of configuration.
218For example, the user might want to overwrite the name that is printed in the `hello_world()` method.
219
220This can be achieved with custom attributes:
221
222```rust,ignore
223#[derive(HelloWorld)]
224#[HelloWorldName = "the best Pancakes"]
225struct Pancakes;
226
227fn main() {
228 Pancakes::hello_world();
229}
230```
231
232If we try to compile this though, the compiler will respond with an error:
233
234```bash
235error: The attribute `HelloWorldName` is currently unknown to the compiler and may have meaning added to it in the future (see issue #29642)
236```
237
238The compiler needs to know that we're handling this attribute and to not respond with an error.
239This is done in the `hello-world-derive` crate by adding `attributes` to the `proc_macro_derive` attribute:
240
241```rust,ignore
242#[proc_macro_derive(HelloWorld, attributes(HelloWorldName))]
243pub fn hello_world(input: TokenStream) -> TokenStream
244```
245
246Multiple attributes can be specified that way.
247
248## Raising Errors
249
250Let's assume that we do not want to accept enums as input to our custom derive method.
251
252This condition can be easily checked with the help of `syn`.
253But how do we tell the user, that we do not accept enums?
254The idiomatic way to report errors in procedural macros is to panic:
255
256```rust,ignore
7cac9316 257fn impl_hello_world(ast: &syn::DeriveInput) -> quote::Tokens {
8bb4bdeb
XL
258 let name = &ast.ident;
259 // Check if derive(HelloWorld) was specified for a struct
260 if let syn::Body::Struct(_) = ast.body {
261 // Yes, this is a struct
262 quote! {
263 impl HelloWorld for #name {
264 fn hello_world() {
265 println!("Hello, World! My name is {}", stringify!(#name));
266 }
267 }
268 }
269 } else {
270 //Nope. This is an Enum. We cannot handle these!
271 panic!("#[derive(HelloWorld)] is only defined for structs, not for enums!");
272 }
273}
274```
275
276If a user now tries to derive `HelloWorld` from an enum they will be greeted with following, hopefully helpful, error:
277
278```bash
279error: custom derive attribute panicked
280 --> src/main.rs
281 |
282 | #[derive(HelloWorld)]
283 | ^^^^^^^^^^
284 |
285 = help: message: #[derive(HelloWorld)] is only defined for structs, not for enums!
286```