What is the purpose of quote::ToTokens::to_tokens for converting syntax elements into token streams?
ToTokens::to_tokens converts Rust syntax elements into TokenStream values, enabling procedural macros to generate code by treating syntax types as token-emitting objects rather than working directly with raw token sequences. The trait provides a uniform interface for converting any syntax elementâwhether a syn type like Ident or LitInt, a primitive like bool or u32, or a custom typeâinto a TokenStream that can be interpolated into quote! macros. This abstraction allows procedural macros to compose code generation using familiar Rust syntax rather than manually constructing token trees, making macro implementations more readable and maintainable.
The ToTokens Trait Definition
use proc_macro2::TokenStream;
use quote::quote;
// Simplified trait definition:
// pub trait ToTokens {
// fn to_tokens(&self, tokens: &mut TokenStream);
//
// fn to_token_stream(&self) -> TokenStream {
// let mut tokens = TokenStream::new();
// self.to_tokens(&mut tokens);
// tokens
// }
// }
fn trait_explanation() {
// to_tokens takes a mutable TokenStream and appends to it
// This design allows efficient chaining without allocating intermediate streams
// to_token_stream is a provided method that creates a new TokenStream
// It calls to_tokens internally
}The trait defines how any type contributes tokens to a stream.
Basic Usage with quote!
use quote::{quote, ToTokens};
use proc_macro2::TokenStream;
fn basic_usage() {
let name = "my_function";
let value = 42u32;
// quote! uses ToTokens for interpolation
// #name calls ToTokens::to_tokens on the value
let tokens: TokenStream = quote! {
fn #name() -> u32 {
#value
}
};
// The # interpolation works because:
// - &str implements ToTokens (creates an ident)
// - u32 implements ToTokens (creates a literal)
println!("{}", tokens);
// fn my_function () -> u32 { 42 }
}The # interpolation in quote! relies on ToTokens implementations.
How quote! Uses ToTokens
use quote::quote;
use proc_macro2::TokenStream;
fn how_quote_works() {
let ident = proc_macro2::Ident::new("example", proc_macro2::Span::call_site());
// When you write #ident in quote!, it:
// 1. Calls ident.to_tokens(&mut output_stream)
// 2. The ident appends itself as an identifier token
// When you write #value where value: u32
// 1. Calls value.to_tokens(&mut output_stream)
// 2. The u32 appends itself as a literal token
let tokens = quote! {
let #ident = #42u32;
};
// Behind the scenes:
let mut output = TokenStream::new();
output.extend(quote!(let ));
ident.to_tokens(&mut output);
output.extend(quote!( = ));
42u32.to_tokens(&mut output);
output.extend(quote!(;));
}quote! transforms #var into var.to_tokens() calls.
Built-in Implementations
use quote::quote;
fn builtin_implementations() {
// Primitive types implement ToTokens:
// Integers become literals
let int_tokens = quote! { #42i32 };
// 42i32
// Floats become literals
let float_tokens = quote! { #3.14f64 };
// 3.14f64
// bool becomes true/false
let bool_tokens = quote! { #true };
// true
// char becomes a char literal
let char_tokens = quote! { #'a' };
// 'a'
// &str becomes an identifier
let str_tokens = quote! { #"hello" };
// hello (identifier, not string literal!)
// String becomes a string literal
let string_tokens = quote! { #"hello".to_string() };
// Wait, this is different - use the String directly
let s = String::from("hello");
let string_tokens = quote! { #s };
// This won't work - String doesn't implement ToTokens directly
}Many built-in types implement ToTokens for code generation.
Working with syn Types
use quote::quote;
use syn::{Ident, LitInt, LitStr, Path};
fn syn_types() {
// syn types all implement ToTokens
// Ident - an identifier
let ident: Ident = syn::parse_quote!(my_function);
let tokens = quote! { fn #ident() {} };
// fn my_function() {}
// LitInt - integer literal
let lit: LitInt = syn::parse_quote!(42);
let tokens = quote! { let x = #lit; };
// let x = 42;
// LitStr - string literal
let lit: LitStr = syn::parse_quote!("hello");
let tokens = quote! { println!(#lit); };
// println!("hello");
// Path - a type path
let path: Path = syn::parse_quote!(std::collections::HashMap);
let tokens = quote! { type Map = #path; };
// type Map = std::collections::HashMap;
}All syn syntax types implement ToTokens to emit their token representation.
The to_tokens Method
use quote::{quote, ToTokens};
use proc_macro2::TokenStream;
fn to_tokens_method() {
let ident = proc_macro2::Ident::new("x", proc_macro2::Span::call_site());
// to_tokens appends to an existing stream
let mut stream = TokenStream::new();
ident.to_tokens(&mut stream);
// to_token_stream creates a new stream
let stream2 = ident.to_token_stream();
// Both produce the same result, but:
// - to_tokens is more efficient for chaining
// - to_token_stream is more convenient for single use
// In quote!, to_tokens is used internally
let combined = quote! {
let #stream = 1;
};
}to_tokens appends to a stream; to_token_stream creates a new one.
Custom ToTokens Implementation
use quote::{quote, ToTokens};
use proc_macro2::TokenStream;
// A custom struct that we want to emit as tokens
struct FieldDefinition {
name: String,
ty: String,
}
impl ToTokens for FieldDefinition {
fn to_tokens(&self, tokens: &mut TokenStream) {
// Emit: name: ty,
let name = proc_macro2::Ident::new(&self.name, proc_macro2::Span::call_site());
let ty = proc_macro2::Ident::new(&self.ty, proc_macro2::Span::call_site());
// Use quote! to build tokens, then extend
tokens.extend(quote! {
#name: #ty,
});
}
}
fn custom_impl() {
let field = FieldDefinition {
name: "count".to_string(),
ty: "i32".to_string(),
};
// Now we can use FieldDefinition in quote!
let tokens = quote! {
struct MyStruct {
#field
}
};
// Equivalent to:
// struct MyStruct {
// count: i32,
// }
}Implement ToTokens for custom types to use them in quote!.
Building Complex Tokens
use quote::{quote, ToTokens, TokenStreamExt};
use proc_macro2::TokenStream;
struct StructBuilder {
name: String,
fields: Vec<(String, String)>,
}
impl ToTokens for StructBuilder {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = proc_macro2::Ident::new(&self.name, proc_macro2::Span::call_site());
// Start with struct declaration
tokens.extend(quote! {
struct #name {
});
// Add each field
for (field_name, field_type) in &self.fields {
let fname = proc_macro2::Ident::new(field_name, proc_macro2::Span::call_site());
let ftype = proc_macro2::Ident::new(field_type, proc_macro2::Span::call_site());
tokens.extend(quote! {
#fname: #ftype,
});
}
// Close the struct
tokens.extend(quote! {
}
});
}
}
fn complex_tokens() {
let builder = StructBuilder {
name: "Person".to_string(),
fields: vec![
("name".to_string(), "String".to_string()),
("age".to_string(), "u32".to_string()),
],
};
let tokens = quote! { #builder };
// struct Person { name: String, age: u32, }
}to_tokens can build complex token sequences by extending the stream.
Interpolation with Different Types
use quote::quote;
use syn::{Ident, Type, PathSegment, PathArguments, Path};
fn interpolation_types() {
let name: Ident = syn::parse_quote!(my_var);
let ty: Type = syn::parse_quote!(Vec<String>);
// Idents, types, and paths all work in quote!
let tokens = quote! {
let #name: #ty = Default::default();
};
// The ToTokens impl for Type handles complex types
// struct, enum, fn types, etc. all work
// Collections also work:
let fields = vec!["a", "b", "c"];
let tokens = quote! {
struct MyStruct {
#(#fields: i32),*
}
};
// Uses ToTokens on each &str in the iterator
}Any type implementing ToTokens can be interpolated.
Repetition with ToTokens
use quote::quote;
fn repetition() {
let names = vec!["a", "b", "c"];
// #(#var),* repeats var, calling to_tokens on each
let tokens = quote! {
#(#names),*
};
// a, b, c
// Each "name" (a &str) has to_tokens called
// &str's ToTokens impl creates an Ident
// For more control, use Ident directly:
let idents: Vec<proc_macro2::Ident> = names
.iter()
.map(|n| proc_macro2::Ident::new(n, proc_macro2::Span::call_site()))
.collect();
let tokens = quote! {
#(#idents: i32),*
};
// a: i32, b: i32, c: i32
}Repetition in quote! calls to_tokens on each element.
ToTokens for Option Types
use quote::quote;
fn option_types() {
// Option<T> where T: ToTokens
// - Some(T) emits T's tokens
// - None emits nothing
let maybe_ident: Option<proc_macro2::Ident> = Some(proc_macro2::Ident::new("x", proc_macro2::Span::call_site()));
let tokens = quote! {
let #maybe_ident = 1;
};
// let x = 1;
let none: Option<proc_macro2::Ident> = None;
let tokens = quote! {
let #none = 1;
};
// let = 1; (nothing emitted for None)
// Useful for optional attributes:
let visibility: Option<proc_macro2::Ident> = Some(proc_macro2::Ident::new("pub", proc_macro2::Span::call_site()));
let tokens = quote! {
#visibility fn my_function() {}
};
// pub fn my_function() {}
}Option<T> where T: ToTokens emits T or nothing.
Token Stream Composition
use quote::{quote, ToTokens};
use proc_macro2::TokenStream;
fn composition() {
let mut stream = TokenStream::new();
// Build tokens piece by piece
let ident = proc_macro2::Ident::new("x", proc_macro2::Span::call_site());
ident.to_tokens(&mut stream);
// Append an equals sign
quote!(=).to_tokens(&mut stream);
// Append a value
42u32.to_tokens(&mut stream);
// stream is now: x = 42
// Use in another quote!
let full = quote! {
let #stream;
};
// let x = 42;
}Compose token streams by calling to_tokens on multiple values.
Working with Spans
use quote::quote;
use proc_macro2::{Span, Ident};
fn spans() {
// Spans carry location information
// When to_tokens is called, the span is preserved
let span = Span::call_site();
let ident = Ident::new("my_ident", span);
// The ident's span is included in the emitted tokens
// This affects error messages and hygiene
let tokens = quote! { #ident };
// In procedural macros, spans matter for:
// 1. Error message locations
// 2. Macro hygiene (what identifiers are visible)
// 3. IDE support (go to definition)
}Spans are preserved through to_tokens, affecting error reporting and hygiene.
Implementing ToTokens for Enum
use quote::{quote, ToTokens};
use proc_macro2::TokenStream;
use syn::Ident;
enum DataType {
Primitive(String),
Generic { name: String, args: Vec<String> },
}
impl ToTokens for DataType {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
DataType::Primitive(name) => {
let ident = Ident::new(name, proc_macro2::Span::call_site());
tokens.extend(quote! { #ident });
}
DataType::Generic { name, args } => {
let name_ident = Ident::new(name, proc_macro2::Span::call_site());
let arg_idents: Vec<_> = args
.iter()
.map(|a| Ident::new(a, proc_macro2::Span::call_site()))
.collect();
tokens.extend(quote! {
#name_ident<#(#arg_idents),*>
});
}
}
}
}
fn enum_impl() {
let ty1 = DataType::Primitive("i32".to_string());
let ty2 = DataType::Generic {
name: "Vec".to_string(),
args: vec!["String".to_string()],
};
let tokens = quote! {
fn process(data: #ty1) -> #ty2 {}
};
// fn process(data: i32) -> Vec<String> {}
}Implement ToTokens for enums to handle different token patterns.
Using with parse_quote!
use quote::quote;
use syn::parse_quote;
use syn::{ItemFn, Block, Ident};
fn with_parse_quote() {
// parse_quote! parses text into syn types
let func: ItemFn = parse_quote! {
fn example() -> i32 {
42
}
};
// syn types implement ToTokens
// So you can emit them back:
let tokens = quote! {
#func
};
// Or extract and modify parts:
let name = &func.sig.ident;
let ret = &func.sig.output;
let new_tokens = quote! {
fn new_#name() #ret {
todo!()
}
};
// fn new_example() -> i32 { todo!() }
}Parse with syn, modify, and emit with ToTokens.
Conditional Token Emission
use quote::{quote, ToTokens};
use proc_macro2::TokenStream;
struct MaybeAttribute {
is_public: bool,
name: String,
}
impl ToTokens for MaybeAttribute {
fn to_tokens(&self, tokens: &mut TokenStream) {
// Conditionally emit pub
if self.is_public {
tokens.extend(quote! { pub });
}
let name = proc_macro2::Ident::new(&self.name, proc_macro2::Span::call_site());
tokens.extend(quote! {
fn #name() {}
});
}
}
fn conditional_emission() {
let public_item = MaybeAttribute {
is_public: true,
name: "visible".to_string(),
};
let private_item = MaybeAttribute {
is_public: false,
name: "hidden".to_string(),
};
let tokens = quote! {
#public_item
#private_item
};
// pub fn visible() {}
// fn hidden() {}
}to_tokens can conditionally emit tokens based on state.
ToTokens vs stringify!
use quote::quote;
fn to_tokens_vs_stringify() {
let ident = proc_macro2::Ident::new("my_ident", proc_macro2::Span::call_site());
// ToTokens preserves structure and spans
let with_to_tokens = quote! { #ident };
// my_ident (as an identifier token)
// stringify! converts to string literal
// This is a different macro - not quote
// But demonstrates the difference:
// ToTokens: produces tokens for code
// stringify: produces a string literal containing the text
// When you use #ident in quote!, it calls to_tokens
// The result is an identifier token, not a string
// To get a string literal of the ident:
let ident_str = ident.to_string();
let tokens = quote! { #ident_str };
// "my_ident"
}ToTokens produces tokens; stringification produces string literals.
Efficiency Considerations
use quote::{quote, ToTokens};
use proc_macro2::TokenStream;
fn efficiency() {
// to_tokens is more efficient than to_token_stream
// when combining multiple items:
let mut stream = TokenStream::new();
// Efficient: appends to existing stream
for i in 0..100 {
i.to_tokens(&mut stream);
}
// Less efficient: creates intermediate streams
// let streams: Vec<TokenStream> = (0..100)
// .map(|i| i.to_token_stream())
// .collect();
// let combined: TokenStream = streams.into_iter().collect();
// quote! handles this internally efficiently
let tokens = quote! {
#((0..100).map(|i| i.to_tokens(&mut stream));)*
};
}Use to_tokens to append efficiently; to_token_stream creates intermediate streams.
Common Patterns in Procedural Macros
use quote::{quote, ToTokens};
use proc_macro2::TokenStream;
use syn::{ItemFn, ReturnType, Ident};
// Common pattern: emit modified version of parsed code
fn emit_modified_function(func: &ItemFn) -> TokenStream {
let name = &func.sig.ident;
let inputs = &func.sig.inputs;
let output = &func.sig.output;
let block = &func.block;
quote! {
#name(#inputs) #output #block
}
}
// Common pattern: emit wrapper function
fn emit_wrapper(name: &Ident, inner: TokenStream) -> TokenStream {
quote! {
fn #name() {
println!("Entering function");
#inner
println!("Exiting function");
}
}
}
// Common pattern: emit struct with derived trait
fn emit_struct_with_trait(name: &str, fields: &[(&str, &str)]) -> TokenStream {
let name = Ident::new(name, proc_macro2::Span::call_site());
let field_names: Vec<_> = fields
.iter()
.map(|(n, _)| Ident::new(n, proc_macro2::Span::call_site()))
.collect();
let field_types: Vec<_> = fields
.iter()
.map(|(_, t)| Ident::new(t, proc_macro2::Span::call_site()))
.collect();
quote! {
#[derive(Debug, Clone)]
struct #name {
#( #field_names: #field_types, )*
}
}
}Procedural macros commonly parse, modify, and emit tokens using ToTokens.
Debugging Token Streams
use quote::quote;
use proc_macro2::TokenStream;
fn debugging() {
let ident = proc_macro2::Ident::new("x", proc_macro2::Span::call_site());
// Debug output shows tokens
let tokens = quote! {
let #ident = 42;
};
// Display output is valid Rust code
println!("Display: {}", tokens);
// let x = 42;
// Debug output shows more detail
println!("Debug: {:?}", tokens);
// TokenStream [...]
// to_string() gives the code as a string
let code = tokens.to_string();
println!("Code: {}", code);
// let x = 42;
}TokenStream implements Display and Debug for inspection.
Synthesis
Quick reference:
use quote::{quote, ToTokens};
use proc_macro2::TokenStream;
fn quick_reference() {
// ToTokens trait: converts types to TokenStream
// fn to_tokens(&self, tokens: &mut TokenStream)
// fn to_token_stream(&self) -> TokenStream // provided
// Built-in implementations for:
// - Primitives: u8, u32, i32, bool, char, &str, String
// - Token types: Ident, Literal, Punct, Group
// - syn types: Item, Expr, Type, Path, etc.
// - Option<T> where T: ToTokens
// Use in quote!:
let name = "my_function";
let value = 42u32;
let tokens = quote! {
fn #name() -> i32 { #value }
};
// Implement for custom types:
struct MyType { name: String }
impl ToTokens for MyType {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = proc_macro2::Ident::new(&self.name, proc_macro2::Span::call_site());
tokens.extend(quote! { #name });
}
}
// Key benefits:
// - Type-safe code generation
// - Preserves spans for error messages
// - Composable token building
// - Integrates with quote! macro
}Key insight: ToTokens::to_tokens is the foundation that makes quote! work as a pleasant code generation DSL. Rather than manually constructing TokenStream values by pushing individual tokensâa tedious, error-prone processâyou implement ToTokens for your types and let quote! handle composition. The # interpolation operator is syntactic sugar that calls to_tokens on the interpolated value, appending its token representation to the output stream. This design enables a natural, almost "template-like" syntax for generating Rust code while maintaining full type safety and span information. When implementing ToTokens for your own types, you decide exactly which tokens to emitâwhether transforming data into identifiers, generating complex type paths, or conditionally including tokens based on configuration. The trait unifies all syntax types under one interface, making quote! a powerful abstraction over procedural macro code generation.
