Mihir Luthra
Mihir Luthra

Reputation: 6779

Destructor of one struct field depends on other without any correlation

I tried to make a short reproducible example but couldn't reproduce my problem, so I stripped most of the contents from my main code retaining the error.

lib.rs

extern crate proc_macro;

use proc_macro::TokenStream;
use quote::{quote};
use syn::{
    parse_macro_input, Data, DeriveInput, Field, Fields, Ident,
    Type, TypeGenerics,
};

struct IterMapped<Ret, T: Iterator + Clone> {
    iter: T,
    map_func: Box<dyn FnMut(T::Item) -> Ret>,
}

impl<Ret, T: Iterator + Clone> IterMapped<Ret, T> {
    pub fn new(iter: T, map_func: Box<dyn FnMut(T::Item) -> Ret>) -> Self {
        IterMapped { iter, map_func }
    }
}

enum BuilderField<'a> {
    Optional(&'a Field, &'a Type),
    Mandatory(&'a Field),
}

struct BuilderData<'a> {
    struct_name: &'a Ident,
    builder_struct_name: &'a Ident,
    ty_generics: &'a TypeGenerics<'a>,
    fields_mapped_iter: IterMapped<BuilderField<'a>, syn::punctuated::Iter<'a, Field>>,
}

#[proc_macro_derive(Builder)]
pub fn derive(input: TokenStream) -> TokenStream {
    let derive_input = parse_macro_input!(input as DeriveInput);

    let data = &derive_input.data;
 
    let struct_name = &derive_input.ident;
    let (impl_generics, ty_generics_temp, where_clause) = derive_input.generics.split_for_impl();
 
    let fields_iter = match data {
        Data::Struct(ref data_struct) => match data_struct.fields {
            Fields::Named(ref fields_named) => fields_named.named.iter(),
            Fields::Unnamed(_) | Fields::Unit => {
                panic!()
            }
        },
        _ => panic!()
    };
 
    let fields_mapped_iter = IterMapped::new(
        fields_iter,
        Box::new(|field| BuilderField::Mandatory(field)),
    );

    let ref ty_generics = ty_generics_temp;
    let ref builder_struct_name = Ident::new(
        &format!("{}Builder", struct_name),
        proc_macro2::Span::call_site(),
    );

    let mut builder_data = BuilderData {
        struct_name,
        builder_struct_name,
        ty_generics,
        fields_mapped_iter,
    };

    TokenStream::from(quote!())
}

Cargo.toml

[package]
name = "derive_builder"
version = "0.0.0"
edition = "2018"
autotests = false
publish = false

[lib]
proc-macro = true

[dependencies]
syn = "1.0.48"
quote = "1.0.7"
proc-macro2 = "1.0.24"

Running it produces the following error:

error[E0716]: temporary value dropped while borrowed
  --> builder/src/lib.rs:58:35
   |
58 |       let ref builder_struct_name = Ident::new(
   |  ___________________________________^
59 | |         &format!("{}Builder", struct_name),
60 | |         proc_macro2::Span::call_site(),
61 | |     );
   | |_____^ creates a temporary which is freed while still in use
...
71 |   }
   |   -
   |   |
   |   temporary value is freed at the end of this statement
   |   borrow might be used here, when `fields_mapped_iter` is dropped and runs the destructor for type `IterMapped<BuilderField<'_>, syn::punctuated::Iter<'_, syn::data::Field>>`
   |
   = note: consider using a `let` binding to create a longer lived value

I don't understand why dropping builder_struct_name's temporary effects fields_mapped_iter. Those two are separate fields.

If I change BuilderData to use lifetime 'b, the code compiles and works correctly:

struct BuilderData<'a, 'b> {
    struct_name: &'a Ident,
    builder_struct_name: &'a Ident,
    ty_generics: &'a TypeGenerics<'a>,
    fields_mapped_iter: IterMapped<BuilderField<'b>, syn::punctuated::Iter<'b, Field>>,
}

☝️Changing BuilderData to this makes the code work.

If I rearrange the order in which the struct fields were declared and move let ref builder_struct_name = … before setting of fields_iter and fields_mapped_iter it works because those 2 are then dropped before builder_struct_name's temp. Why do their destructors rely on builder_struct_name's temp?

Upvotes: 0

Views: 132

Answers (1)

eggyal
eggyal

Reputation: 125925

When a scope ends, variables are dropped in reverse of the order in which they were declared. Hence BuilderData's lifetime parameter 'a cannot extend beyond when builder_struct_name is dropped to the full life of fields_mapped_iter. However, syn::punctuated::Iter requires that its type argument T outlives its lifetime argument 'a.

Upvotes: 1

Related Questions