Questions tagged [rust-proc-macros]

Use this tag for questions about procedural macros, declared in crates of the proc-macro type, of the Rust programming language.

The Rust compiler (rustc) can accept various plugins called procedural macros to modify the user's code and generate it at compile-time.

The documentation can be found in the official book.

The kinds of procedural macros

There are 3 kinds of procedural macros.

The custom derives

They are used as following:

#[derive(MyCustomDerive)]
struct Foo {
    // ...
}

This kind of macros applies to a decorated struct or enum and are intended to generate some code without modifying the user's code, typically to generate an implementation of a trait.

There are some examples of such macros in the std crate, for example to generate the implementation of the Debug or the PartialEq traits.

The custom attributes

They are used as following:

#[my_custom_attribute(optional_parameter = "value")]
fn some_function() {
    // ...
}

They can modify arbitrarily any Rust item: a struct, a function, a module, etc.

Function-like macro

They are used as following:

my_function_like_macro!(some arbitrary input);

It can accept any input to generate some Rust code: the only limit of the input format is the imagination of the macro developer.

How to create a procedural macro

A procedural macro must live in its own crate. A special line must be added in the Cargo.toml manifest:

[lib]
proc-macro = true

The following dependencies are not mandatory, but are widely used because of the useful things they bring:

  • proc-macro2 let use some unstable proc-macro things inside a project compiled in stable;
  • quote let generate some Rust code easily;
  • syn is a Rust source parser.

The macros must be declared in the root module (i.e. in the lib.rs file), for example for a custom attribute:

#[proc_macro_attribute]
pub fn fact_inner(args: TokenStream, input: TokenStream) -> TokenStream {
    /// ...
}

Every procedural macro returns a TokenStream being the generated code.

The functions for each kind of procedural have a different signature:

Custom derive

A custom derive must be declared as following:

#[proc_macro_derive(MyDerive)]
pub fn my_derive(input: TokenStream) -> TokenStream {
    // ...
}

input is the user defined struct or enum being decorated.

A typical implementation will look like the following:

#[proc_macro_derive(MyDerive)]
pub fn my_derive(input: TokenStream) -> TokenStream {
    // Parse the input as a struct:
    let item = syn::parse_macro_input!(input as syn::ItemStruct);
    // Or if you want to decorate an enum:
    let item = syn::parse_macro_input!(input as syn::ItemEnum);

    // Get the generated code, or transform an error into a compile error:
    let output = my_derive_generate(item).unwrap_or_else(|err| err.to_compile_error());

    TokenStream::from(output)
}

fn my_derive_generate(input: syn::ItemStruct) -> Result<proc_macro2::TokenStream, syn::parse::Error> {
    // ...
}

You can accept attributes in the user defined code:

#[proc_macro_derive(MyDerive, attributes(my_attribute))]
pub fn my_derive(input: TokenStream) -> TokenStream {
    // ...
}

Example of accepted code from the user:

#[derive(MyDerive)]
struct Foo {
    #[my_attribute("any content")]
    bar: i32,
}

Custom attribute

It must be declared like this:

#[proc_macro_attribute]
pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
    // ...
}

args is the arguments of the attribute, while input is the user's input where the macro applies.

Here are the valid attribute contents:

  • No content: #[attribute]
  • Name = value: #[attribute = "a literal"]
  • A literal: #[attribute("a literal")]
  • A list: #[attribute(Ident1, Ident2)]
  • A list of key/value: #[attribute(key = literal1, key = literal2)]

A typical implementation will look like the following:

#[proc_macro_attribute]
pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
    // Parse the attribute arguments:
    let args = syn::parse_macro_input!(args as syn::AttributeArgs);
    // Parse the input (for example):
    let item = syn::parse_macro_input!(input as syn::ItemFn);

    // Get the generated code, or transform an error into a compile error:
    let output = my_attribute_generate(item).unwrap_or_else(|err| err.to_compile_error());

    TokenStream::from(output)
}

fn my_attribute_generate(args: syn::AttributeArgs, input: syn::ItemStruct)
    -> Result<proc_macro2::TokenStream, syn::parse::Error> {
    // ...
}

Function-like macro

It must be declared like this:

#[proc_macro]
pub fn my_proc_macro(input: TokenStream) -> TokenStream {
    // ...
}
126 questions
0
votes
1 answer

Call proc macro inside other proc macro

I have a small reproduction project which fails to compile. The project can be downloaded here: https://github.com/Jasperav/proc_macro_collision. The error is: error[E0659]: `proc_macro_call` is ambiguous (macro-expanded name vs less macro-expanded…
J. Doe
  • 12,159
  • 9
  • 60
  • 114
0
votes
1 answer

Calling function recursively on all fields, and subfields in procmacro

I have a derive style procedural macro, where i would like to calculate the potential maximum length of the serialized version of a struct. For example, given the struct TestStruct below, i would like to call some function with all the fields to…
smilykoch
  • 57
  • 8
0
votes
0 answers

How to handle `Self` in Rust procedual macros?

I'm trying to create a derive macro in Rust to create instances, as an example, which should implement following trait: pub trait MyTrait { fn my_default() -> Option; } I use syn and quote to parse and generate the AST, as…
Galaxy
  • 1,129
  • 11
  • 27
0
votes
1 answer

Convert enum variants with named data into separate structs using procedural macros

I'm writing a procedural macro to convert the variants of an enum into individual structs and implement some traits for that struct. This works fine for unit and unnamed variants, but variants with named data will cause it to silently fail :). Here…
-1
votes
1 answer

Why does a procedural macro not see environment variables set by dotenv in my build script?

I have a procedural macro crate Proc and binary crate Bin. Bin has a dependency on Proc. Proc needs a filled environment variable to function properly. This is some code inside my build.rs in Bin. Proc can successfully find the env value when using…
J. Doe
  • 12,159
  • 9
  • 60
  • 114
-2
votes
1 answer

Why is this procedural macro interpreting this expression as a function?

So my current code has a procedural macro that is interpreting this algebraic expression as a function this is the error error: expected expression, found keyword `fn` --> src\main.rs:5:5 | 5 | symbolic!(x^2 + 2*x) | …
1 2 3
8
9