1. 1. Introduction
  2. 2. Getting Started
  3. 3. Tutorial: Guessing Game
  4. 4. Syntax and Semantics
    1. 4.1. Variable Bindings
    2. 4.2. Functions
    3. 4.3. Primitive Types
    4. 4.4. Comments
    5. 4.5. if
    6. 4.6. Loops
    7. 4.7. Vectors
    8. 4.8. Ownership
    9. 4.9. References and Borrowing
    10. 4.10. Lifetimes
    11. 4.11. Mutability
    12. 4.12. Structs
    13. 4.13. Enums
    14. 4.14. Match
    15. 4.15. Patterns
    16. 4.16. Method Syntax
    17. 4.17. Strings
    18. 4.18. Generics
    19. 4.19. Traits
    20. 4.20. Drop
    21. 4.21. if let
    22. 4.22. Trait Objects
    23. 4.23. Closures
    24. 4.24. Universal Function Call Syntax
    25. 4.25. Crates and Modules
    26. 4.26. `const` and `static`
    27. 4.27. Attributes
    28. 4.28. `type` aliases
    29. 4.29. Casting between types
    30. 4.30. Associated Types
    31. 4.31. Unsized Types
    32. 4.32. Operators and Overloading
    33. 4.33. Deref coercions
    34. 4.34. Macros
    35. 4.35. Raw Pointers
    36. 4.36. `unsafe`
  5. 5. Effective Rust
    1. 5.1. The Stack and the Heap
    2. 5.2. Testing
    3. 5.3. Conditional Compilation
    4. 5.4. Documentation
    5. 5.5. Iterators
    6. 5.6. Concurrency
    7. 5.7. Error Handling
    8. 5.8. Choosing your Guarantees
    9. 5.9. FFI
    10. 5.10. Borrow and AsRef
    11. 5.11. Release Channels
    12. 5.12. Using Rust without the standard library
    13. 5.13. Procedural Macros (and custom derive)
  6. 6. Nightly Rust
    1. 6.1. Compiler Plugins
    2. 6.2. Inline Assembly
    3. 6.3. No stdlib
    4. 6.4. Intrinsics
    5. 6.5. Lang items
    6. 6.6. Advanced linking
    7. 6.7. Benchmark Tests
    8. 6.8. Box Syntax and Patterns
    9. 6.9. Slice Patterns
    10. 6.10. Associated Constants
    11. 6.11. Custom Allocators
  7. 7. Glossary
  8. 8. Syntax Index
  9. 9. Bibliography

Procedural Macros (and custom Derive)

As you've seen throughout the rest of the book, Rust provides a mechanism called "derive" that lets you implement traits easily. For example,

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}Run

is a lot simpler than

struct Point {
    x: i32,
    y: i32,
}

use std::fmt;

impl fmt::Debug for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Point {{ x: {}, y: {} }}", self.x, self.y)
    }
}Run

Rust includes several traits that you can derive, but it also lets you define your own. We can accomplish this task through a feature of Rust called "procedural macros." Eventually, procedural macros will allow for all sorts of advanced metaprogramming in Rust, but today, they're only for custom derive.

Let's build a very simple trait, and derive it with custom derive.

Hello World

So the first thing we need to do is start a new crate for our project.

$ cargo new --bin hello-world

All we want is to be able to call hello_world() on a derived type. Something like this:

#[derive(HelloWorld)]
struct Pancakes;

fn main() {
    Pancakes::hello_world();
}Run

With some kind of nice output, like Hello, World! My name is Pancakes..

Let's go ahead and write up what we think our macro will look like from a user perspective. In src/main.rs we write:

#[macro_use]
extern crate hello_world_derive;

trait HelloWorld {
    fn hello_world();
}

#[derive(HelloWorld)]
struct FrenchToast;

#[derive(HelloWorld)]
struct Waffles;

fn main() {
    FrenchToast::hello_world();
    Waffles::hello_world();
}Run

Great. So now we just need to actually write the procedural macro. At the moment, procedural macros need to be in their own crate. Eventually, this restriction may be lifted, but for now, it's required. As such, there's a convention; for a crate named foo, a custom derive procedural macro is called foo-derive. Let's start a new crate called hello-world-derive inside our hello-world project.

$ cargo new hello-world-derive

To make sure that our hello-world crate is able to find this new crate we've created, we'll add it to our toml:

[dependencies]
hello-world-derive = { path = "hello-world-derive" }

As for our the source of our hello-world-derive crate, here's an example:

extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;

#[proc_macro_derive(HelloWorld)]
pub fn hello_world(input: TokenStream) -> TokenStream {
    // Construct a string representation of the type definition
    let s = input.to_string();
    
    // Parse the string representation
    let ast = syn::parse_macro_input(&s).unwrap();

    // Build the impl
    let gen = impl_hello_world(&ast);
    
    // Return the generated impl
    gen.parse().unwrap()
}Run

So there is a lot going on here. We have introduced two new crates: syn and quote. As you may have noticed, input: TokenSteam is immediately converted to a String. This String is a string representation of the Rust code for which we are deriving HelloWorld for. At the moment, the only thing you can do with a TokenStream is convert it to a string. A richer API will exist in the future.

So what we really need is to be able to parse Rust code into something usable. This is where syn comes to play. syn is a crate for parsing Rust code. The other crate we've introduced is quote. It's essentially the dual of syn as it will make generating Rust code really easy. We could write this stuff on our own, but it's much simpler to use these libraries. Writing a full parser for Rust code is no simple task.

The comments seem to give us a pretty good idea of our overall strategy. We are going to take a String of the Rust code for the type we are deriving, parse it using syn, construct the implementation of hello_world (using quote), then pass it back to Rust compiler.

One last note: you'll see some unwrap()s there. If you want to provide an error for a procedural macro, then you should panic! with the error message. In this case, we're keeping it as simple as possible.

Great, so let's write impl_hello_world(&ast).

fn impl_hello_world(ast: &syn::MacroInput) -> quote::Tokens {
    let name = &ast.ident;
    quote! {
        impl HelloWorld for #name {
            fn hello_world() {
                println!("Hello, World! My name is {}", stringify!(#name));
            }
        }
    }
}Run

So this is where quotes comes in. The ast argument is a struct that gives us a representation of our type (which can be either a struct or an enum). Check out the docs, there is some useful information there. We are able to get the name of the type using ast.ident. The quote! macro let's us write up the Rust code that we wish to return and convert it into Tokens. quote! let's us use some really cool templating mechanics; we simply write #name and quote! will replace it with the variable named name. You can even do some repetition similar to regular macros work. You should check out the docs for a good introduction.

So I think that's it. Oh, well, we do need to add dependencies for syn and quote in the cargo.toml for hello-world-derive.

[dependencies]
syn = "0.10.5"
quote = "0.3.10"

That should be it. Let's try to compile hello-world.

error: the `#[proc_macro_derive]` attribute is only usable with crates of the `proc-macro` crate type
 --> hello-world-derive/src/lib.rs:8:3
  |
8 | #[proc_macro_derive(HelloWorld)]
  |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Oh, so it appears that we need to declare that our hello-world-derive crate is a proc-macro crate type. How do we do this? Like this:

[lib]
proc-macro = true

Ok so now, let's compile hello-world. Executing cargo run now yields:

Hello, World! My name is FrenchToast
Hello, World! My name is Waffles

We've done it!