From fefec659f8093d6dfeade85c466989d5124ad228 Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Tue, 1 Apr 2025 20:02:59 +0200 Subject: [PATCH 1/4] Add #[empty_trace] attribute for `Trace` derive macro --- gc/src/empty_trace.rs | 9 ++++++ gc/src/lib.rs | 4 ++- gc/tests/empty_trace.rs | 26 ++++++++++++++++ gc_derive/Cargo.toml | 1 + gc_derive/src/lib.rs | 69 ++++++++++++++++++++++++++++++++--------- 5 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 gc/src/empty_trace.rs create mode 100644 gc/tests/empty_trace.rs diff --git a/gc/src/empty_trace.rs b/gc/src/empty_trace.rs new file mode 100644 index 0000000..65a27b8 --- /dev/null +++ b/gc/src/empty_trace.rs @@ -0,0 +1,9 @@ +use std::rc::Rc; + +/// A marker trait for types that don't require tracing. +/// TODO: Safety conditions +pub unsafe trait EmptyTrace {} + +unsafe impl EmptyTrace for String {} + +unsafe impl EmptyTrace for Rc {} diff --git a/gc/src/lib.rs b/gc/src/lib.rs index 8d086cf..b995c97 100644 --- a/gc/src/lib.rs +++ b/gc/src/lib.rs @@ -30,14 +30,16 @@ mod gc; #[cfg(feature = "serde")] mod serde; mod trace; +mod empty_trace; #[cfg(feature = "derive")] -pub use gc_derive::{Finalize, Trace}; +pub use gc_derive::{Finalize, Trace, EmptyTrace}; // We re-export the Trace method, as well as some useful internal methods for // managing collections or configuring the garbage collector. pub use crate::gc::{finalizer_safe, force_collect}; pub use crate::trace::{Finalize, Trace}; +pub use crate::empty_trace::EmptyTrace; #[cfg(feature = "unstable-config")] pub use crate::gc::{configure, GcConfig}; diff --git a/gc/tests/empty_trace.rs b/gc/tests/empty_trace.rs new file mode 100644 index 0000000..9ecd937 --- /dev/null +++ b/gc/tests/empty_trace.rs @@ -0,0 +1,26 @@ +#[allow(dead_code)] +mod static_tests { + use gc::Gc; + use gc_derive::{EmptyTrace, Finalize, Trace}; + use std::rc::Rc; + + #[derive(EmptyTrace)] + struct StructWithEmptyTrace(Rc); + + #[derive(Trace, Finalize)] + struct Traceable { + #[empty_trace] + a: StructWithEmptyTrace, + + #[empty_trace] + b: T, + } + + fn test_empty_trace() { + let x = Rc::new(String::new()); + Gc::new(Traceable { + a: StructWithEmptyTrace(x.clone()), + b: x.clone(), + }); + } +} diff --git a/gc_derive/Cargo.toml b/gc_derive/Cargo.toml index 9e6a9a3..5f53788 100644 --- a/gc_derive/Cargo.toml +++ b/gc_derive/Cargo.toml @@ -18,3 +18,4 @@ proc-macro = true proc-macro2 = "1.0" quote = "1.0" synstructure = "0.13" +syn = "2.0.95" diff --git a/gc_derive/src/lib.rs b/gc_derive/src/lib.rs index 6a31a65..00be6df 100644 --- a/gc_derive/src/lib.rs +++ b/gc_derive/src/lib.rs @@ -1,7 +1,9 @@ use quote::quote; +use syn::parse_quote_spanned; +use syn::spanned::Spanned; use synstructure::{decl_derive, AddBounds, Structure}; -decl_derive!([Trace, attributes(unsafe_ignore_trace)] => derive_trace); +decl_derive!([Trace, attributes(unsafe_ignore_trace, empty_trace)] => derive_trace); fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { s.filter(|bi| { @@ -10,6 +12,37 @@ fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { .iter() .any(|attr| attr.path().is_ident("unsafe_ignore_trace")) }); + + // We also implement drop to prevent unsafe drop implementations on this + // type and encourage people to use Finalize. This implementation will + // call `Finalize::finalize` if it is safe to do so. + let drop_impl = s.unbound_impl( + quote!(::std::ops::Drop), + quote! { + fn drop(&mut self) { + if ::gc::finalizer_safe() { + ::gc::Finalize::finalize(self); + } + } + }, + ); + + // Separate all the bindings that were annotated with `#[empty_trace]` + let empty_trace_bindings = s.drain_filter(|bi| { + bi.ast() + .attrs + .iter() + .any(|attr| attr.path().is_ident("empty_trace")) + }); + + // Require the annotated bindings to implement `EmptyTrace` + for variant in empty_trace_bindings.variants() { + for binding in variant.bindings() { + let ty = &binding.ast().ty; + s.add_where_predicate(parse_quote_spanned!(ty.span()=> #ty: ::gc::EmptyTrace)); + } + } + let trace_body = s.each(|bi| quote!(mark(#bi))); s.add_bounds(AddBounds::Fields); @@ -52,20 +85,6 @@ fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { }, ); - // We also implement drop to prevent unsafe drop implementations on this - // type and encourage people to use Finalize. This implementation will - // call `Finalize::finalize` if it is safe to do so. - let drop_impl = s.unbound_impl( - quote!(::std::ops::Drop), - quote! { - fn drop(&mut self) { - if ::gc::finalizer_safe() { - ::gc::Finalize::finalize(self); - } - } - }, - ); - quote! { #trace_impl #drop_impl @@ -78,3 +97,23 @@ decl_derive!([Finalize] => derive_finalize); fn derive_finalize(s: Structure<'_>) -> proc_macro2::TokenStream { s.unbound_impl(quote!(::gc::Finalize), quote!()) } + +decl_derive!([EmptyTrace] => derive_empty_trace); + +// TODO: Does not work on self-referential types +fn derive_empty_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { + // Add where bounds for all bindings manually because synstructure only adds them if they depend on one of the parameters. + let mut where_predicates = Vec::new(); + for v in s.variants() { + for bi in v.bindings() { + let ty = &bi.ast().ty; + let span = ty.span(); + where_predicates.push(parse_quote_spanned! { span=> #ty: ::gc::EmptyTrace }); + } + } + for p in where_predicates { + s.add_where_predicate(p); + } + s.add_bounds(AddBounds::None); + s.unsafe_bound_impl(quote! { ::gc::EmptyTrace }, quote! {}) +} From 36c57ccb88e013ff912a8af3e911bbde608a0816 Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Wed, 2 Apr 2025 21:45:50 +0200 Subject: [PATCH 2/4] Support self-referential types in `EmptyTrace` derive macro --- gc/src/empty_trace.rs | 4 +++ gc/tests/empty_trace.rs | 12 +++---- gc_derive/src/lib.rs | 72 ++++++++++++++++++++++++++++++++--------- 3 files changed, 65 insertions(+), 23 deletions(-) diff --git a/gc/src/empty_trace.rs b/gc/src/empty_trace.rs index 65a27b8..d1f95bf 100644 --- a/gc/src/empty_trace.rs +++ b/gc/src/empty_trace.rs @@ -7,3 +7,7 @@ pub unsafe trait EmptyTrace {} unsafe impl EmptyTrace for String {} unsafe impl EmptyTrace for Rc {} + +unsafe impl EmptyTrace for Box {} + +unsafe impl EmptyTrace for Option {} \ No newline at end of file diff --git a/gc/tests/empty_trace.rs b/gc/tests/empty_trace.rs index 9ecd937..6b5177e 100644 --- a/gc/tests/empty_trace.rs +++ b/gc/tests/empty_trace.rs @@ -1,11 +1,10 @@ #[allow(dead_code)] mod static_tests { - use gc::Gc; - use gc_derive::{EmptyTrace, Finalize, Trace}; + use gc::{EmptyTrace, Finalize, Gc, Trace}; use std::rc::Rc; - + #[derive(EmptyTrace)] - struct StructWithEmptyTrace(Rc); + struct StructWithEmptyTrace(Rc>); #[derive(Trace, Finalize)] struct Traceable { @@ -17,10 +16,9 @@ mod static_tests { } fn test_empty_trace() { - let x = Rc::new(String::new()); Gc::new(Traceable { - a: StructWithEmptyTrace(x.clone()), - b: x.clone(), + a: StructWithEmptyTrace(Rc::new(None)), + b: Rc::new(String::new()), }); } } diff --git a/gc_derive/src/lib.rs b/gc_derive/src/lib.rs index 00be6df..d258aa1 100644 --- a/gc_derive/src/lib.rs +++ b/gc_derive/src/lib.rs @@ -1,7 +1,7 @@ -use quote::quote; -use syn::parse_quote_spanned; +use quote::{format_ident, quote, ToTokens}; use syn::spanned::Spanned; -use synstructure::{decl_derive, AddBounds, Structure}; +use syn::{parse_quote_spanned, GenericParam, WherePredicate}; +use synstructure::{decl_derive, AddBounds, Structure, VariantInfo}; decl_derive!([Trace, attributes(unsafe_ignore_trace, empty_trace)] => derive_trace); @@ -100,20 +100,60 @@ fn derive_finalize(s: Structure<'_>) -> proc_macro2::TokenStream { decl_derive!([EmptyTrace] => derive_empty_trace); -// TODO: Does not work on self-referential types -fn derive_empty_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { - // Add where bounds for all bindings manually because synstructure only adds them if they depend on one of the parameters. - let mut where_predicates = Vec::new(); - for v in s.variants() { - for bi in v.bindings() { +fn derive_empty_trace(s: Structure<'_>) -> proc_macro2::TokenStream { + let s_ast = &s.ast(); + let name = &s_ast.ident; + let temp_name = format_ident!("_{name}"); + let params = &s_ast.generics.params; + let param_names = params + .iter() + .map(|p| match p { + GenericParam::Lifetime(p) => p.to_token_stream(), + GenericParam::Type(p) => p.ident.to_token_stream(), + GenericParam::Const(p) => p.ident.to_token_stream(), + }) + .collect::>(); + let where_predicates = &s_ast + .generics + .where_clause + .iter() + .flat_map(|wc| &wc.predicates) + .collect::>(); + + // Require that all bindings implement `EmptyTrace` + let bindings = s.variants().iter().flat_map(VariantInfo::bindings); + let additional_where_predicates: Vec = bindings + .map(|bi| { let ty = &bi.ast().ty; let span = ty.span(); - where_predicates.push(parse_quote_spanned! { span=> #ty: ::gc::EmptyTrace }); - } - } - for p in where_predicates { - s.add_where_predicate(p); + parse_quote_spanned! { span=> #ty: ::gc::EmptyTrace } + }) + .collect(); + + // If any bindings in `s` refer to `s` itself then trait resolution could run into a cycle through our generated where predicates. + // We solve this with the following hack: + // Locally, we rename `s` and replace it with a temporary type of the same shape. + // That type unconditionally implements `EmptyTrace`, which might, technically, be unsafe but is fine since we never instantiate that type. + // Its only purpose is to stand in for `s` inside the generated predicates in order to break the cycle. + quote! { + const _: () = { + type #temp_name<#params> = #name<#(#param_names),*>; + { + #s_ast + + unsafe impl<#params> ::gc::EmptyTrace + for #name<#(#param_names),*> + where + #(#where_predicates),* + {} + + unsafe impl<#params> ::gc::EmptyTrace + for #temp_name<#(#param_names),*> + where + #(#where_predicates),* + #(#additional_where_predicates),* + {} + } + }; } - s.add_bounds(AddBounds::None); - s.unsafe_bound_impl(quote! { ::gc::EmptyTrace }, quote! {}) } From 915446c502d8d794332c46102d0f856f3260b669 Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Thu, 3 Apr 2025 23:43:15 +0200 Subject: [PATCH 3/4] Add basic trait implementations involving `EmptyTrace` --- gc/src/empty_trace.rs | 13 ------- gc/src/lib.rs | 6 +-- gc/src/trace.rs | 86 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 21 deletions(-) delete mode 100644 gc/src/empty_trace.rs diff --git a/gc/src/empty_trace.rs b/gc/src/empty_trace.rs deleted file mode 100644 index d1f95bf..0000000 --- a/gc/src/empty_trace.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::rc::Rc; - -/// A marker trait for types that don't require tracing. -/// TODO: Safety conditions -pub unsafe trait EmptyTrace {} - -unsafe impl EmptyTrace for String {} - -unsafe impl EmptyTrace for Rc {} - -unsafe impl EmptyTrace for Box {} - -unsafe impl EmptyTrace for Option {} \ No newline at end of file diff --git a/gc/src/lib.rs b/gc/src/lib.rs index b995c97..43e78f6 100644 --- a/gc/src/lib.rs +++ b/gc/src/lib.rs @@ -30,16 +30,14 @@ mod gc; #[cfg(feature = "serde")] mod serde; mod trace; -mod empty_trace; #[cfg(feature = "derive")] -pub use gc_derive::{Finalize, Trace, EmptyTrace}; +pub use gc_derive::{EmptyTrace, Finalize, Trace}; // We re-export the Trace method, as well as some useful internal methods for // managing collections or configuring the garbage collector. pub use crate::gc::{finalizer_safe, force_collect}; -pub use crate::trace::{Finalize, Trace}; -pub use crate::empty_trace::EmptyTrace; +pub use crate::trace::{EmptyTrace, Finalize, Trace}; #[cfg(feature = "unstable-config")] pub use crate::gc::{configure, GcConfig}; diff --git a/gc/src/trace.rs b/gc/src/trace.rs index a68e72f..72fc8b6 100644 --- a/gc/src/trace.rs +++ b/gc/src/trace.rs @@ -1,4 +1,5 @@ use std::borrow::{Cow, ToOwned}; +use std::cell::{Cell, OnceCell, RefCell}; use std::collections::hash_map::{DefaultHasher, RandomState}; use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; use std::hash::BuildHasherDefault; @@ -11,6 +12,7 @@ use std::num::{ }; use std::path::{Path, PathBuf}; use std::rc::Rc; +use std::sync::{Arc, Mutex, OnceLock, RwLock}; use std::sync::atomic::{ AtomicBool, AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicU16, AtomicU32, AtomicU64, AtomicU8, AtomicUsize, @@ -105,10 +107,60 @@ macro_rules! custom_trace { }; } -impl Finalize for &'static T {} -unsafe impl Trace for &'static T { +/// A marker trait for types that don't require tracing. +/// +/// # Safety +/// TODO: Safety conditions +pub unsafe trait EmptyTrace {} + +// TODO: The README needs to be updated to explain when `Rc` and the other types here can be managed by GC. +impl Finalize for Rc {} +unsafe impl Trace for Rc { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for Rc {} + +impl Finalize for Arc {} +unsafe impl Trace for Arc { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for Arc {} + +impl Finalize for RefCell {} +unsafe impl Trace for RefCell { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for RefCell {} + +impl Finalize for Cell {} +unsafe impl Trace for Cell { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for Cell {} + +impl Finalize for OnceCell {} +unsafe impl Trace for OnceCell { unsafe_empty_trace!(); } +unsafe impl EmptyTrace for OnceCell {} + +impl Finalize for Mutex {} +unsafe impl Trace for Mutex { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for Mutex {} + +impl Finalize for RwLock {} +unsafe impl Trace for RwLock { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for RwLock {} + +impl Finalize for OnceLock {} +unsafe impl Trace for OnceLock { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for OnceLock {} macro_rules! simple_empty_finalize_trace { ($($T:ty),*) => { @@ -117,6 +169,8 @@ macro_rules! simple_empty_finalize_trace { impl Finalize for $T {} #[allow(deprecated)] unsafe impl Trace for $T { unsafe_empty_trace!(); } + #[allow(deprecated)] + unsafe impl EmptyTrace for $T {} )* } } @@ -141,7 +195,6 @@ simple_empty_finalize_trace![ char, String, str, - Rc, Path, PathBuf, NonZeroIsize, @@ -172,6 +225,13 @@ simple_empty_finalize_trace![ RandomState ]; +// We don't care about non-static references because they can never be owned by a `Gc`. +impl Finalize for &'static T {} +unsafe impl Trace for &'static T { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for &'static T {} + impl Finalize for [T; N] {} unsafe impl Trace for [T; N] { custom_trace!(this, { @@ -180,11 +240,13 @@ unsafe impl Trace for [T; N] { } }); } +unsafe impl EmptyTrace for [T; N] {} macro_rules! fn_finalize_trace_one { ($ty:ty $(,$args:ident)*) => { impl Finalize for $ty {} unsafe impl Trace for $ty { unsafe_empty_trace!(); } + unsafe impl EmptyTrace for $ty {} } } macro_rules! fn_finalize_trace_group { @@ -215,6 +277,7 @@ macro_rules! tuple_finalize_trace { $(mark($args);)* }); } + unsafe impl<$($args: $crate::EmptyTrace),*> EmptyTrace for ($($args,)*) {} } } @@ -249,6 +312,7 @@ unsafe impl Trace for Box { mark(&**this); }); } +unsafe impl EmptyTrace for Box {} impl Finalize for [T] {} unsafe impl Trace for [T] { @@ -258,6 +322,7 @@ unsafe impl Trace for [T] { } }); } +unsafe impl EmptyTrace for [T] {} impl Finalize for Vec {} unsafe impl Trace for Vec { @@ -267,6 +332,7 @@ unsafe impl Trace for Vec { } }); } +unsafe impl EmptyTrace for Vec {} impl Finalize for Option {} unsafe impl Trace for Option { @@ -276,6 +342,7 @@ unsafe impl Trace for Option { } }); } +unsafe impl EmptyTrace for Option {} impl Finalize for Result {} unsafe impl Trace for Result { @@ -286,6 +353,7 @@ unsafe impl Trace for Result { } }); } +unsafe impl EmptyTrace for Result {} impl Finalize for BinaryHeap {} unsafe impl Trace for BinaryHeap { @@ -295,6 +363,7 @@ unsafe impl Trace for BinaryHeap { } }); } +unsafe impl EmptyTrace for BinaryHeap {} impl Finalize for BTreeMap {} unsafe impl Trace for BTreeMap { @@ -305,6 +374,7 @@ unsafe impl Trace for BTreeMap { } }); } +unsafe impl EmptyTrace for BTreeMap {} impl Finalize for BTreeSet {} unsafe impl Trace for BTreeSet { @@ -314,6 +384,7 @@ unsafe impl Trace for BTreeSet { } }); } +unsafe impl EmptyTrace for BTreeSet {} impl Finalize for HashMap {} unsafe impl Trace for HashMap { @@ -325,6 +396,7 @@ unsafe impl Trace for HashMap { } }); } +unsafe impl EmptyTrace for HashMap {} impl Finalize for HashSet {} unsafe impl Trace for HashSet { @@ -335,6 +407,7 @@ unsafe impl Trace for HashSet { } }); } +unsafe impl EmptyTrace for HashSet {} impl Finalize for LinkedList {} unsafe impl Trace for LinkedList { @@ -344,11 +417,13 @@ unsafe impl Trace for LinkedList { } }); } +unsafe impl EmptyTrace for LinkedList {} impl Finalize for PhantomData {} unsafe impl Trace for PhantomData { unsafe_empty_trace!(); } +unsafe impl EmptyTrace for PhantomData {} impl Finalize for VecDeque {} unsafe impl Trace for VecDeque { @@ -358,8 +433,9 @@ unsafe impl Trace for VecDeque { } }); } +unsafe impl EmptyTrace for VecDeque {} -impl<'a, T: ToOwned + ?Sized> Finalize for Cow<'a, T>{} +impl<'a, T: ToOwned + ?Sized> Finalize for Cow<'a, T> {} unsafe impl<'a, T: ToOwned + ?Sized> Trace for Cow<'a, T> where T::Owned: Trace, @@ -370,8 +446,10 @@ where } }); } +unsafe impl<'a, T: ToOwned + ?Sized> EmptyTrace for Cow<'a, T> where T::Owned: EmptyTrace {} impl Finalize for BuildHasherDefault {} unsafe impl Trace for BuildHasherDefault { unsafe_empty_trace!(); } +unsafe impl EmptyTrace for BuildHasherDefault {} From d46f1ff5cb801b4b8879be2a1af31648466b7728 Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Thu, 3 Apr 2025 23:48:15 +0200 Subject: [PATCH 4/4] Remove `#[empty_trace]` attribute for `Trace` derive macro --- gc/tests/empty_trace.rs | 24 --------------------- gc_derive/src/lib.rs | 47 +++++++++++++---------------------------- 2 files changed, 15 insertions(+), 56 deletions(-) delete mode 100644 gc/tests/empty_trace.rs diff --git a/gc/tests/empty_trace.rs b/gc/tests/empty_trace.rs deleted file mode 100644 index 6b5177e..0000000 --- a/gc/tests/empty_trace.rs +++ /dev/null @@ -1,24 +0,0 @@ -#[allow(dead_code)] -mod static_tests { - use gc::{EmptyTrace, Finalize, Gc, Trace}; - use std::rc::Rc; - - #[derive(EmptyTrace)] - struct StructWithEmptyTrace(Rc>); - - #[derive(Trace, Finalize)] - struct Traceable { - #[empty_trace] - a: StructWithEmptyTrace, - - #[empty_trace] - b: T, - } - - fn test_empty_trace() { - Gc::new(Traceable { - a: StructWithEmptyTrace(Rc::new(None)), - b: Rc::new(String::new()), - }); - } -} diff --git a/gc_derive/src/lib.rs b/gc_derive/src/lib.rs index d258aa1..dea15f6 100644 --- a/gc_derive/src/lib.rs +++ b/gc_derive/src/lib.rs @@ -3,7 +3,7 @@ use syn::spanned::Spanned; use syn::{parse_quote_spanned, GenericParam, WherePredicate}; use synstructure::{decl_derive, AddBounds, Structure, VariantInfo}; -decl_derive!([Trace, attributes(unsafe_ignore_trace, empty_trace)] => derive_trace); +decl_derive!([Trace, attributes(unsafe_ignore_trace)] => derive_trace); fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { s.filter(|bi| { @@ -12,37 +12,6 @@ fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { .iter() .any(|attr| attr.path().is_ident("unsafe_ignore_trace")) }); - - // We also implement drop to prevent unsafe drop implementations on this - // type and encourage people to use Finalize. This implementation will - // call `Finalize::finalize` if it is safe to do so. - let drop_impl = s.unbound_impl( - quote!(::std::ops::Drop), - quote! { - fn drop(&mut self) { - if ::gc::finalizer_safe() { - ::gc::Finalize::finalize(self); - } - } - }, - ); - - // Separate all the bindings that were annotated with `#[empty_trace]` - let empty_trace_bindings = s.drain_filter(|bi| { - bi.ast() - .attrs - .iter() - .any(|attr| attr.path().is_ident("empty_trace")) - }); - - // Require the annotated bindings to implement `EmptyTrace` - for variant in empty_trace_bindings.variants() { - for binding in variant.bindings() { - let ty = &binding.ast().ty; - s.add_where_predicate(parse_quote_spanned!(ty.span()=> #ty: ::gc::EmptyTrace)); - } - } - let trace_body = s.each(|bi| quote!(mark(#bi))); s.add_bounds(AddBounds::Fields); @@ -85,6 +54,20 @@ fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { }, ); + // We also implement drop to prevent unsafe drop implementations on this + // type and encourage people to use Finalize. This implementation will + // call `Finalize::finalize` if it is safe to do so. + let drop_impl = s.unbound_impl( + quote!(::std::ops::Drop), + quote! { + fn drop(&mut self) { + if ::gc::finalizer_safe() { + ::gc::Finalize::finalize(self); + } + } + }, + ); + quote! { #trace_impl #drop_impl