2 unstable releases
0.2.0 | Jan 7, 2023 |
---|---|
0.1.0 | Jan 6, 2023 |
#388 in Procedural macros
2,799 downloads per month
Used in 8 crates
(4 directly)
8KB
A fast code generation macro
Easily & efficiently generate code by quoting it:
use proc_macro2::TokenStream;
use quote_into::quote_into;
let mut stream = TokenStream::new();
quote_into!(stream += println!("hello world!"));
assert_eq!(stream.to_string(), "println ! (\"hello world!\")");
Variable interpolation
You can interpolate any value that implements ToTokens
using the variable's name prefixed with #
:
use proc_macro2::TokenStream;
use quote_into::quote_into;
let mut stream = TokenStream::new();
let variable = false;
quote_into!(stream += let boolean = #variable;);
assert_eq!(stream.to_string(), "let boolean = false ;");
Interpolation blocks
You can insert a block of Rust code using #{}
. Inside, you can make function calls, use conditionals, or loops, or anything else allowed in a code block.
use proc_macro2::TokenStream;
use quote_into::quote_into;
let mut stream = TokenStream::new();
quote_into!(stream += let array = [#{
for i in 0..3 {
quote_into!(stream += Some(#i),)
}
}];);
assert_eq!(stream.to_string(), "let array = [Some (0i32) , Some (1i32) , Some (2i32) ,] ;");
Note: if you have a group in the quoted tokens (such as {}
, ()
, or []
), a new TokenStream
is created. In the example above, the outer stream
is not the same as the stream
inside the #{}
interpolation, since it is wrapped in []
.
Expression interpolation
You can also interpolate any Rust expression using #()
:
use proc_macro2::TokenStream;
use quote_into::quote_into;
let mut s = TokenStream::new();
quote_into!(s += const ANSWER: u32 = #(7 * 6););
assert_eq!(s.to_string(), "const ANSWER : u32 = 42i32 ;");
Comparison with quote
The de-facto standard for code generation is the quote
crate. This crate proposes small improvements to its API and performance.
API
Quote seems to be largely inspired by macro_rules
when it comes to loop interpolations. While expressive and concise, they are a bit hard to learn and read in my opinion.
Instead, quote_into
provides a simple "interpolation block" #{ }
where you can write regular Rust code. Since there is basically no additional syntax, using interpolation blocks should feel more intuitive.
Performance
quote
returns a new TokenStream
, while quote_into
accepts a TokenStream
to append tokens to. While this makes quote
a bit less verbose, it's also slightly less efficient when it comes to combining quoted fragments. Often, code generation happens in different functions which are then combined. The recommended way to do this is to create intermediate TokenStream
s, and interpolate them using #
:
use quote::quote;
let type_definition = quote! {...};
let methods = quote! {...};
let tokens = quote! {
#type_definition
#methods
};
The additional allocations have a small performance impact even if you only create a few TokenStreams
and combine them. A more extreme case is a deeply recursive function, which repeatedly constructs and interpolates many token streams. For example, consider the below functions, which generate code of the form Box<Box<...<bool>>>
:
use quote::quote;
use quote_into::quote_into;
use proc_macro2::TokenStream;
// Using quote:
fn quote_box_wrap(levels: usize) -> TokenStream {
match levels {
0 => quote!(bool),
other => {
let inner = quote_box_wrap(other - 1);
quote!(Box<#inner>)
}
}
}
// Using quote_into:
fn quote_into_box_wrap(levels: usize) -> TokenStream {
fn inner(s: &mut TokenStream, levels: usize) {
match levels {
0 => quote_into!(s += bool),
other => quote_into! {s += Box < #{
inner(s, other - 1)
} >},
}
}
// there's a bit of boilerplate, but only once per macro
let mut s = TokenStream::new();
inner(&mut s, levels);
s
}
Results for levels=100
:
quote: 591.14 µs
quote_into: 17.247 µs
Since the version using quote
has to create many intermediate TokenStreams
, it is much slower in this case. Again, this is an extreme example. It's unlikely your code generation will actually be 30x faster if you switch to quote_into
, unless you're inefficiently nesting interpolated TokenStreams
several levels deep as above.
Status: prototype
While you can technically use quote_into
in your projects, it's only in the prototype stage.
- The API is subject to change, as there are a few things I'm unsure about
- It's implemented using
quote
... I'm not sure how "bad" this is (it might increase compile times), but I have a feeling it should be rewritten using manual token generation code at some point.
Dependencies
~1.5MB
~36K SLoC