quote

repository·master·Indexed 23 days ago

https://github.com/dtolnay/quote

A Rust quasi-quoting library providing the `quote!` macro to transform Rust syntax tree data structures into `proc_macro2::TokenStream`s. It is primarily used in procedural macro development to generate code, featuring support for variable interpolation, repetitions, and span control via `quote_spanned!`. The crate also provides the `ToTokens` trait for custom type interpolation and the `format_ident!` macro for constructing identifiers.

Tokens
4.2K
Snippets
13
Records
21
Agent score
80%

What's inside quote

  1. How repetition works in quote!

    master

    Repetition is performed using #(...)* or #(...),* syntax, similar to macro_rules!. This iterates through elements of an interpolated variable (such as a Vec, slice, BTreeSet, or any Iterator) and inserts the repetition body for each element.

    • #(#var)* — No separators.
    • #(#var),* — Uses the character before the asterisk as a separator (e.g., a comma) and does not produce a trailing separator.
    • #(#var ,)* — Produces a trailing separator.
    • Repetitions can contain multiple interpolations or other code structures.
  2. Convert TokenStream for procedural macros

    master
    The quote! macro produces a proc_macro2::TokenStream. However, Rust procedural macros require a proc_macro::TokenStream. You can convert between them using .into() or proc_macro::TokenStream::from(tokens).
  3. How to make method calls with interpolated types

    master

    When invoking a method on a type that is being interpolated (e.g., field_type::new()), the expansion might result in invalid syntax for certain types like Vec<i32>. To ensure correct syntax (e.g., <Vec<i32>>::new()), wrap the interpolated type in angle brackets.

    # use quote::quote;
    #
    # let field_type = quote!(...);
    #
    // Use angle brackets for correct syntax with complex types
    quote! {
        let value = <#field_type>::new();
    }
  4. How to index into tuple structs using `syn::Index`

    master

    When interpolating indices for tuple or tuple struct access, do not interpolate raw integers (e.g., self.#i), as this produces invalid syntax like self.0usize. Instead, interpolate the index as a syn::Index type to ensure it appears as a proper index literal (e.g., self.0).

    # use proc_macro2::{Ident, TokenStream};
    # use quote::{ToTokens, TokenStreamExt};
    #
    # mod syn {
    #     use proc_macro2::{Literal, TokenStream};
    #     use quote::{ToTokens, TokenStreamExt};
    #
    #     pub struct Index(usize);
    #
    #     impl From<usize> for Index {
    #         fn from(i: usize) -> Self {
    #             Index(i)
    #         }
    #     }
    #     impl ToTokens for Index {
    #         fn to_tokens(&self, tokens: &mut TokenStream) {
    #             tokens.append(Literal::usize_unsuffixed(self.0));
    #         }
    #     }
    # }
    # struct Struct {
    #     fields: Vec<Ident>,
    # }
    # impl Struct {
    #     fn example(&self) -> TokenStream {
    # let i = (0..self.fields.len()).map(syn::Index::from);
    #
    // Correctly expands to self.0, self.1, etc.
    quote! {
        0 #( + self.#i.heap_size() )*
    }
    #     }
    # }
  5. Construct identifiers with format_ident!

    master

    Interpolating an identifier directly next to other tokens (e.g., _#ident) will not concatenate them; they will remain separate tokens. To create a new identifier by modifying an existing one, use the format_ident! macro.

    let varname = format_ident!("_{}", ident);
    quote! {
        let mut #varname = 0;
    }
  6. Make safe method calls with turbofish syntax

    master

    When calling methods on a type provided via interpolation (like field_type), using #field_type::new() can fail if the type requires turbofish syntax (e.g., Vec<i32>). To ensure correctness for all types, wrap the interpolated type in angle brackets: <#field_type>::new().

    quote! {
        let value = <#field_type>::new();
    }
  7. Combine quoted fragments

    master

    Since tokens produced by quote! implement ToTokens, you can build complex TokenStreams incrementally by interpolating previously generated fragments into new quote! calls.

    let type_definition = quote! {...};
    let methods = quote! {...};
    
    let tokens = quote! {
        #type_definition
        #methods
    };
  8. Generate formatted code in build.rs

    master

    When writing generated code to a file (e.g., in build.rs), the conversion from tokens to source code is lossy and lacks hygiene/span information. To make the output human-readable and easier to debug, pass the tokens through prettyplease before writing to disk.

    let output = quote! { ... };
    let syntax_tree = syn::parse2(output).unwrap();
    let formatted = prettyplease::unparse(&syntax_tree);
    
    let out_dir = env::var_os("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("out.rs");
    fs::write(dest_path, formatted).unwrap();
  9. Run the quote-benchmark suite

    master
    To benchmark the performance of the quote crate in both debug and release modes, use the following command. This will run the benchmark for both macro-based and non-macro-based code generation across both build profiles.
  10. Control hygiene with quote_spanned!

    master
    Tokens generated within a quote! macro are assigned Span::call_site(). If you need to explicitly provide a different span for the interpolated tokens to control hygiene, use the quote_spanned! macro.
  11. Use the quote! macro for interpolation

    master

    The quote! macro allows you to write Rust-like syntax that is packaged into a proc_macro2::TokenStream. You can interpolate variables using the #var syntax. Any type implementing the quote::ToTokens trait (including primitives and most syn types) can be interpolated.

    let tokens = quote! {
        struct SerializeWith #generics #where_clause {
            value: &'a #field_ty,
            phantom: core::marker::PhantomData<#item_ty>,
        }
    
        impl #generics serde::Serialize for SerializeWith #generics #where_clause {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                #path(self.value, serializer)
            }
        }
    
        SerializeWith {
            value: #value,
            phantom: core::marker::PhantomData::<#item_ty>,
        }
    };