Search code examples
rustquote

How do I use an iterator twice inside of the quote! macro?


I'm trying to implement the builder pattern from the proc macro workshop I'm creating a proc macro which parses a struct, extracts its name, field_names and field_types. It should reproduce the struct itself and also create a builder struct with the same field_names but with optional types.

My problem is that field_name and field_type are iterators that I would have to use twice in order to create two structs out of one.

This is my source tree

.
├── Cargo.lock
├── Cargo.toml
├── builder-derive
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
└── src
    └── main.rs

./cargo.toml

[package]
name = "proc-macro-question"
version = "0.1.0"
authors = ["ropottnik <[email protected]>"]
edition = "2018"

[dependencies]
builder-derive = { path = "./builder-derive" }

./main.rs

#[derive(Builder)]
struct SomeStruct {
    some_field: i32,
}

fn main() {
    println!("Hello, world!");
}

./builder-derive/cargo.toml

[package]
name = "builder-derive"
version = "0.1.0"
authors = ["ropottnik <[email protected]>"]
edition = "2018"

[lib]
proc-macro = true

[dev-dependencies]
trybuild = { version = "1.0", features = ["diff"] }

[dependencies]
syn = { version= "1.0", features = ["extra-traits"] }
quote = "1.0"

./builder-derive/src/lib.rs

#[proc_macro_derive(Builder)]
pub fn derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let builder_name = format_ident!("{}Builder", &name);
    let fields = match &input.data {
        Data::Struct(DataStruct {
            fields: Fields::Named(fields),
            ..
        }) => &fields.named,
        _ => panic!("expected a struct with named fields"),
    };

    let field_name = fields.iter().map(|field| &field.ident);
    let field_type = fields.iter().map(|field| &field.ty);

    let expanded = quote! {
        pub struct #name {
            #(#field_name: #field_type,)*
        }

        pub struct #builder_name {
            #(#field_name: Option<#field_type>,)*
        }
    };

    expanded.into()
}

$ cargo run output

warning: unused import: `Ident`
 --> builder-derive/src/lib.rs:1:18
  |
1 | use proc_macro::{Ident, TokenStream};
  |                  ^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

error[E0382]: use of moved value: `field_name`
  --> builder-derive/src/lib.rs:22:20
   |
19 |       let field_name = fields.iter().map(|field| &field.ident);
   |           ---------- move occurs because `field_name` has type `Map<syn::punctuated::Iter<'_, syn::Field>, [closure@builder-derive/src/lib.rs:19:40: 19:60]>`, which does not implement the `Copy` trait
...
22 |       let expanded = quote! {
   |  ____________________^
23 | |         pub struct #name {
24 | |             #(#field_name: #field_type,)*
25 | |         }
...  |
29 | |         }
30 | |     };
   | |     ^
   | |     |
   | |_____`field_name` moved due to this method call
   |       value used here after move
   |
note: this function consumes the receiver `self` by taking ownership of it, which moves `field_name`
  --> /Users/simon/.cargo/registry/src/github.com-1ecc6299db9ec823/quote-1.0.8/src/runtime.rs:53:28
   |
53 |         fn quote_into_iter(self) -> (Self, HasIter) {
   |                            ^^^^
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0382]: use of moved value: `field_type`
  --> builder-derive/src/lib.rs:22:20
   |
20 |       let field_type = fields.iter().map(|field| &field.ty);
   |           ---------- move occurs because `field_type` has type `Map<syn::punctuated::Iter<'_, syn::Field>, [closure@builder-derive/src/lib.rs:20:40: 20:57]>`, which does not implement the `Copy` trait
21 |
22 |       let expanded = quote! {
   |  ____________________^
23 | |         pub struct #name {
24 | |             #(#field_name: #field_type,)*
25 | |         }
...  |
29 | |         }
30 | |     };
   | |     ^
   | |     |
   | |_____`field_type` moved due to this method call
   |       value used here after move
   |
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

I guess I could create one iterator per usage but that seems impossibly dumb to me ;-)


Solution

  • Iterators can only be used zero or one time, not multiple; that's standard Rust and doesn't involve the quote! macro:

    fn example() {
        let nums = std::iter::empty::<i32>();
        for _ in nums {}
        for _ in nums {}
    }
    
    error[E0382]: use of moved value: `nums`
       --> src/lib.rs:4:14
        |
    2   |     let nums = std::iter::empty::<i32>();
        |         ---- move occurs because `nums` has type `std::iter::Empty<i32>`, which does not implement the `Copy` trait
    3   |     for _ in nums {}
        |              ----
        |              |
        |              `nums` moved due to this implicit call to `.into_iter()`
        |              help: consider borrowing to avoid moving into the for loop: `&nums`
    4   |     for _ in nums {}
        |              ^^^^ value used here after move
        |
    note: this function consumes the receiver `self` by taking ownership of it, which moves `nums`
    

    Even if you took the iterators by mutable reference, iterating through it once would exhaust it, leaving no values for the second usage.

    You will need to clone the iterator:

    use quote::quote; // 1.0.8
    
    fn example() {
        let nums = std::iter::empty::<i32>();
        let nums2 = nums.clone();
    
        quote! {
            #(#nums)*
            #(#nums2)*
        };
    }
    

    You can also collect the iterator into a Vec and iterate over it multiple times:

    use quote::quote; // 1.0.8
    
    fn example() {
        let nums = std::iter::empty();
        let nums: Vec<i32> = nums.collect();
        
        quote! {
            #(#nums)*
            #(#nums)*
        };
    }