12

Now that proc_macros have been stabilized, how does one create such a thing?

From what I've seen, there's the option of putting a #[proc_macro_attribute] annotation on a fn whatsitsname(attrs: TokenStream, code: TokenStream) -> TokenStream, but how can I register it? How can I add custom attributes?

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
llogiq
  • 13,815
  • 8
  • 40
  • 72
  • 1
    For what it's worth, the book contains a [section on writing procedural macros](https://doc.rust-lang.org/book/2018-edition/appendix-04-macros.html#procedural-macros-for-custom-derive). – Sven Marnach Oct 01 '18 at 09:14
  • 1
    @SvenMarnach this only explains `proc_macro_derive`, not `proc_macro_attribute`. – llogiq Oct 01 '18 at 10:48
  • @llogiq I'm aware of that, however the link may be useful to other people landing here. (And your first question is "how does one create such a thing", which is covered there.) – Sven Marnach Oct 01 '18 at 11:25
  • I would say that they should be documented in the Book appendix linked by @SvenMarnach and if they are not, it is a bug that should be reported *and* referenced in the tracking issue https://github.com/rust-lang/rust/issues/38356 (it is still open, and does not mention documenting the feature, but probably should). – Jan Hudec Oct 01 '18 at 13:08

2 Answers2

21

The Rust compiler has a fairly complete test suite. When looking for examples of newly-introduced features, I frequently start there:

$ rg -c proc_macro_attribute
src/test/run-pass-fulldeps/auxiliary/proc_macro_def.rs:2
src/test/ui-fulldeps/auxiliary/attr_proc_macro.rs:1
[... 35 other matches ...]

Here's a fully worked example:

$ tree
.
├── Cargo.toml
├── my_macro
│   ├── Cargo.toml
│   ├── src
│   │   └── lib.rs
└── src
    └── main.rs

Cargo.toml

We add a dependency on our macro-defining crate.

[package]
name = "foo"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]

[dependencies]
my_macro = { path = "my_macro" }

src/main.rs

We import the attribute macro and add it to a function.

#[macro_use]
extern crate my_macro;

#[log_entry_and_exit(hello, "world")]
fn this_will_be_destroyed() -> i32 {
    42
}

fn main() {
    dummy()
}

my_macro/Cargo.toml

We specify crate_type as proc_macro.

[package]
name = "my_macro"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]

[lib]
crate_type = ["proc-macro"]

my_macro/src/lib.rs

We add #[proc_macro_attribute] to each function that should be a macro.

extern crate proc_macro;

use proc_macro::*;

#[proc_macro_attribute]
pub fn log_entry_and_exit(args: TokenStream, input: TokenStream) -> TokenStream {
    let x = format!(r#"
        fn dummy() {{
            println!("entering");
            println!("args tokens: {{}}", {args});
            println!("input tokens: {{}}", {input});
            println!("exiting");
        }}
    "#,
            args = args.into_iter().count(),
            input = input.into_iter().count(),
    );

    x.parse().expect("Generated invalid tokens")
}

cargo run

entering
args tokens: 3
input tokens: 7
exiting

The "hard" part is wrangling the TokenStream into something useful and then outputting something equally useful. The crates syn and quote are the current gold standards for those two tasks. Dealing with TokenStream is covered in the macros chapter of The Rust Programming Language as well as API documentation.

There's also #[proc_macro], which takes functions of the form:

#[proc_macro]
pub fn the_name_of_the_macro(input: TokenStream) -> TokenStream

And can be invoked as the_name_of_the_macro!(...).

cafce25
  • 15,907
  • 4
  • 25
  • 31
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
3

If I understand RFC 1566 correctly, you:

  • Create a crate of type proc_macro, i.e. its Cargo.toml should contain
[lib]
proc-macro = true
  • In that crate, create the implementation, annotated with #[proc_macro_attribute]. The #[proc_macro] for function-like macros and #[proc_macro_derive] for custom derives work the same, except they only have one TokenStream argument. These are defined in the proc_macro crate.

    The first token stream is the arguments in the attribute, the second is the body of the annotated item.

  • Then in the crate that wants to use the macro, simply depend on the proc_macro crate and import it with #[macro_use] attribute (#[macro_use] extern crate …).

That should be enough.

The appendix in Book should be extended to mention the other proc macro types beyond #[proc_macro_derive]. That it does not is probably a bug.

cafce25
  • 15,907
  • 4
  • 25
  • 31
Jan Hudec
  • 73,652
  • 13
  • 125
  • 172