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 unstableproc-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 {
// ...
}