dtolnay / quote

Rust quasi-quoting
Apache License 2.0
1.32k stars 90 forks source link

Best practice for implementing `ToTokens` for more complex types e.g. structs? #260

Closed mfreeborn closed 11 months ago

mfreeborn commented 1 year ago

I was just wondering what the best way to implement ToTokens is for structs (and enums) that I am generating in a build.rs file. For arguments sake, I'll give a couple of options using an example struct with intended output being a tuple struct.

use proc_macro2::{Delimiter, Group, Ident}

struct SomeStruct {
    name: String,
    ty: String
}

// Option 1
impl ToTokens for SomeStruct {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.append(Ident::new("pub", Span::call_site()));
        tokens.append(Ident::new("struct", Span::call_site()));
        tokens.append(Ident::new(self.name, Span::call_site()));

        let ty = format_ident!("{}", self.ty);
        tokens.append(Group::new(Delimiter::Parenthesis, quote! { #ty }));
    }
}

// Option 2
impl ToTokens for SomeStruct {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = format_ident!("{}", self.name);
        let ty = format_ident!("{}", self.ty);
        tokens.append(
            Group::new(
                Delimiter::None,
                quote! {
                    pub struct #name(#ty);
                }
            )
        );
    }
}

// Option 3
// None of the above

Adding something to the docs, or creating an examples directory for this crate, could be a useful addition.

giuseppe998e commented 1 year ago

I found myself doing:

impl ToTokens for SomeStruct {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = format_ident!("{}", self.name);
        let ty = format_ident!("{}", self.ty);

        quote! {
            pub struct #name(#ty);
            // ...
        }
        .to_tokens(tokens);
    }
}

But this actually creates a temporary allocation in the heap since the quote! macro internally creates a new TokenStream.
It would be nice if there was a variant of the macro that accepts a given TokenStream (in this case the variable tokens).

dtolnay commented 11 months ago

What I would most likely write is:

use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};

impl ToTokens for SomeStruct {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = Ident::new(&self.name, Span::call_site());
        let ty = Ident::new(&self.ty, Span::call_site());
        tokens.extend(quote! {
            pub struct #name(#ty);
        });
    }
}