Third-Party Crates
Note: Crates beyond the automatically linked
proc_macro
crate are not required to write procedural macros. The crates listed here merely make writing them simpler and more concise, while potentially adding to the compilation time of the procedural macro due to added dependencies.
As procedural macros live in a crate they can naturally depend on (crates.io) crates. turns out the crate ecosystem has some really helpful crates tailored towards procedural macros that this chapter will quickly go over, most of which will be used in the following chapters to implement the example macros. As these are merely quick introductions it is advised to look at each crate's documentation for more in-depth information if required.
proc-macro2
proc-macro2
, the successor of the proc_macro
crate! Or so you might think but that is of course not correct, the name might be a bit misleading.
This crate is actually just a wrapper around the proc_macro
crate serving two specific purposes, taken from the documentation:
- Bring proc-macro-like functionality to other contexts like build.rs and main.rs.
- Make procedural macros unit testable.
As the proc_macro
crate is exclusive to proc_macro
type crates, making them unit testable or accessing them from non-proc macro code is next to impossible.
With that in mind the proc-macro2
crate mimics the original proc_macro
crate's api, acting as a wrapper in proc-macro crates and standing on its own in non-proc-macro crates.
Hence it is advised to build libraries targeting proc-macro code to be built against proc-macro2
instead as that will enable those libraries to be unit testable, which is also the reason why the following listed crates take and emit proc-macro2::TokenStream
s instead.
When a proc_macro
token stream is required, one can simply .into()
the proc-macro2
token stream to get the proc_macro
version and vice-versa.
Procedural macros using the proc-macro2
crate will usually import the proc-macro2::TokenStream
in an aliased form like use proc-macro2::TokenStream as TokenStream2
.
quote
The quote
crate mainly exposes just one macro, the quote!
macro.
This little macro allows you to easily create token streams by writing the actual source out as syntax while also giving you the power of interpolating tokens right into the written syntax.
Interpolation can be done by using the #local
syntax where local refers to a local in the current scope.
Likewise #( #local )*
can be used to interpolate over an iterator of types that implement ToTokens
, this works similar to declarative macro_rules!
repetitions in that they allow a separator as well as extra tokens inside the repetition.
let name = /* some identifier */;
let exprs = /* an iterator over expressions tokenstreams */;
let expanded = quote! {
impl SomeTrait for #name { // #name interpolates the name local from above
fn some_function(&self) -> usize {
#( #exprs )* // #name interpolates exprs by iterating the iterator
}
}
};
This a very useful tool when preparing macro output avoiding the need of creating a token stream by inserting tokens one by one.
Note: As stated earlier, this crate makes use of
proc_macro2
and thus thequote!
macro returns aproc-macro2::TokenStream
.
syn
The syn
crate is a parsing library for parsing a stream of Rust tokens into a syntax tree of Rust source code.
It is a very powerful library that makes parsing proc-macro input quite a bit easier, as the proc_macro
crate itself does not expose any kind of parsing capabilities, merely the tokens.
As the library can be a heavy compilation dependency, it makes heavy use of feature gates to allow users to cut it as small as required.
So what does it offer? A bunch of things.
First of all it has definitions and parsing for all standard Rust syntax nodes(when the full
feature is enabled), as well as a DeriveInput
type which encapsulates all the information a derive macro gets passed as an input stream as a structured input(requires the derive
feature, enabled by default). These can be used right out of the box with the parse_macro_input!
macro(requires the parsing
and proc-macro
features, enabled by default) to parse token streams into these types.
If Rust syntax doesn't cut it, and instead one wishes to parse custom non-Rust syntax the crate also offers a generic parsing API, mainly in the form of the Parse
trait(requires the parsing
feature, enabled by default).
Aside from this the types exposed by the library keep location information and spans which allows procedural macros to emit detailed error messages pointing at the macro input at the points of interest.
As this is again a library for procedural macros, it makes use of the proc_macro2
token streams and spans and as such, conversions may be required.