43

I'm trying to write my own derive mode macro in Rust, and the documentation on it is somewhat lacking in examples.

I have a struct like:

#[derive(MyMacroHere)]
struct Example {
    id: i64,
    value: Option<String>,
}

I want my macro to generate a method à la

fn set_fields(&mut self, id: i64, value: Option<String>) {
    // ...
}

What are the basic steps to use the TokenStream trait to achieve something like that?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Lev
  • 1,698
  • 3
  • 18
  • 26
  • 3
    The reference probably isn't the best place to go for documentation on procedural macros - have you read [the 'Macros' appendix from the Rust book](https://doc.rust-lang.org/book/second-edition/appendix-04-macros.html#procedural-macros-for-custom-derive) and the API docs for the [`proc_macro`](https://doc.rust-lang.org/proc_macro/index.html) built-in library? They'd be the best places to start, in my opinion. – Joe Clay Nov 03 '18 at 22:27
  • 3
    The general gist of how procedural macros work is that they take in a `TokenStream` (in this case, it'd be the tokens that make up the definition of `Example`), and then run a piece of code that generates a new `TokenStream` to add to the program (this would be the tokens that make up the `set_fields` definition). People generally use the `syn` crate to translate the input tokens to a proper Rust syntax tree, and the `quote` crate to generate the output. – Joe Clay Nov 03 '18 at 22:35

1 Answers1

50
  1. Create a crate for your procedural macros:

    cargo new my_derive --lib
    
  2. Edit the Cargo.toml to make it a procedural macro crate:

    [lib]
    proc-macro = true
    
  3. Implement your procedural macro:

    extern crate proc_macro;
    
    use proc_macro::TokenStream;
    
    #[proc_macro_derive(MyMacroHere)]
    pub fn my_macro_here_derive(input: TokenStream) -> TokenStream { 
        // ...
    }
    
  4. Import the procedural macro and use it:

    extern crate my_derive;
    
    use my_derive::MyMacroHere;
    
    #[derive(MyMacroHere)]
    struct Example {
        id: i64,
        value: Option<String>,
    }
    

The hard part is in implementation of the macro. Most people use the syn and quote crates to parse the incoming Rust code and then generate new code.

For example, syn's documentation starts with an example of a custom derive. You will parse the struct (or enum or union) and then handle the various ways of defining a struct (unit, tuple, named fields). You'll collect the information you need (type, maybe name), then you'll generate the appropriate code.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 8
    To make parsing easier, I've built [darling](https://crates.io/crates/darling). It exposes a `serde`-like API where you define a struct for the arguments your macro needs, and then pass it a `syn::DeriveInput`. It handles parsing, field validation, duplicate field checking, and error creation. – ehdv Feb 06 '19 at 21:13