Skip to content

Commit 1eabebc

Browse files
committed
Define a single Arena struct and generate a type alias in make_arena
Surprisingly, this doesn't require GATs, and is actually simpler than a GAT-based solution. The core idea is to define a trait: ```rust pub trait RootProvider<'a> { type Root: Collect; } ``` In `Arena`, we use the higher-ranked trait bound `R: for<'a> RootProvider<'a>`. Then, writing `<R as RootProvider<'gc>>::Root` gives us the root type with some arbitrary lifetime `'gc` substituted. The `make_arena` macro can then simply declare the type alias `type MyArenaName = Arena<dyn for<'a> RootProvider<'a, Root = UserRoot<'a>>>` Since a `RootProvider` trait object implements `RootProvider`, the projection `<R as RootProvider<'gc>>::Root` will evaluate to `UserRoot<'gc>`, without the need to define any additional structs or trait impls.
1 parent 96c567d commit 1eabebc

File tree

2 files changed

+137
-111
lines changed

2 files changed

+137
-111
lines changed

src/gc-arena/src/arena.rs

Lines changed: 136 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
use core::{f64, usize};
1+
use core::{f64, mem::ManuallyDrop, usize};
22

3-
use crate::context::{Context, MutationContext};
3+
use crate::{
4+
context::{Context, MutationContext},
5+
Collect,
6+
};
47

58
#[derive(Debug, Clone)]
69
pub struct ArenaParameters {
@@ -94,130 +97,153 @@ impl ArenaParameters {
9497
#[macro_export]
9598
macro_rules! make_arena {
9699
($arena:ident, $root:ident) => {
97-
make_arena!(@impl pub(self) $arena, $root);
100+
make_arena!(pub(self) $arena, $root);
98101
};
99102

100-
($v:vis $arena:ident, $root:ident) => {
101-
make_arena!(@impl $v $arena, $root);
103+
($vis:vis $arena:ident, $root:ident) => {
104+
// Instead of generating an impl of `RootProvider`, we use a trait object.
105+
// The projection `<R as RootProvider<'gc>>::Root` is used to obtain the root
106+
// type with the lifetime `'gc` applied
107+
// By using a trait object, we avoid the need to generate a new type for each
108+
// invocation of this macro, which would lead to name conflicts if the macro was
109+
// used multiple times in the same scope.
110+
$vis type $arena = $crate::Arena<dyn for<'a> $crate::RootProvider<'a, Root = $root<'a>>>;
102111
};
112+
}
103113

104-
(@impl $v:vis $arena:ident, $root:ident) => {
105-
$v struct $arena {
106-
context: $crate::Context,
107-
root: ::core::mem::ManuallyDrop<$root<'static>>,
108-
}
114+
#[doc(hidden)]
115+
/// A helper trait for the `make_arena` macro. Writing
116+
/// <R as RootProvider<'gc>>::Root is similar to writing a GAT `R::Root<'gc>`,
117+
/// but it works on older versions of Rust. Additionally, GATs currently
118+
/// prevent a trait from being object-safe, which prevents the trait object
119+
/// trick we use in `make_arena` from working.
120+
pub trait RootProvider<'a> {
121+
type Root: Collect;
122+
}
109123

110-
impl $arena {
111-
/// Create a new arena with the given garbage collector tuning parameters. You must
112-
/// provide a closure that accepts a `MutationContext` and returns the appropriate root.
113-
/// The held root type is immutable inside the arena, in order to provide mutation, you
114-
/// must use `GcCell` types inside the root.
115-
#[allow(unused)]
116-
pub fn new<F>(arena_parameters: $crate::ArenaParameters, f: F) -> $arena
117-
where
118-
F: for<'gc> FnOnce($crate::MutationContext<'gc, '_>) -> $root<'gc>,
119-
{
120-
unsafe {
121-
let context = $crate::Context::new(arena_parameters);
122-
let root: $root<'static> = ::std::mem::transmute(f(context.mutation_context()));
123-
$arena {
124-
context: context,
125-
root: ::core::mem::ManuallyDrop::new(root),
126-
}
127-
}
128-
}
124+
pub struct Arena<R: for<'a> RootProvider<'a> + ?Sized> {
125+
context: crate::Context,
126+
root: ManuallyDrop<<R as RootProvider<'static>>::Root>,
127+
}
129128

130-
/// Similar to `new`, but allows for constructor that can fail.
131-
#[allow(unused)]
132-
pub fn try_new<F, E>(
133-
arena_parameters: $crate::ArenaParameters,
134-
f: F,
135-
) -> Result<$arena, E>
136-
where
137-
F: for<'gc> FnOnce($crate::MutationContext<'gc, '_>) -> Result<$root<'gc>, E>,
138-
{
139-
unsafe {
140-
let context = $crate::Context::new(arena_parameters);
141-
let root: $root = f(context.mutation_context())?;
142-
let root: $root<'static> = ::std::mem::transmute(root);
143-
Ok($arena {
144-
context: context,
145-
root: ::core::mem::ManuallyDrop::new(root),
146-
})
147-
}
129+
impl<R: for<'a> RootProvider<'a> + ?Sized> Arena<R> {
130+
/// Create a new arena with the given garbage collector tuning parameters. You must
131+
/// provide a closure that accepts a `MutationContext` and returns the appropriate root.
132+
/// The held root type is immutable inside the arena, in order to provide mutation, you
133+
/// must use `GcCell` types inside the root.
134+
#[allow(unused)]
135+
pub fn new<F>(arena_parameters: crate::ArenaParameters, f: F) -> Arena<R>
136+
where
137+
F: for<'gc> FnOnce(crate::MutationContext<'gc, '_>) -> <R as RootProvider<'gc>>::Root,
138+
{
139+
unsafe {
140+
let context = crate::Context::new(arena_parameters);
141+
// Note - we transmute the `MutationContext` to a `'static` lifetime here,
142+
// instead of transmuting the root type returned by `f`. Transmuting the root
143+
// type is allowed in nightly versions of rust
144+
// (see https://github.com/rust-lang/rust/pull/101520#issuecomment-1252016235)
145+
// but is not yet stable. Transmuting the `MutationContext` is completely invisible
146+
// to the callback `f` (since it needs to handle an arbitrary lifetime),
147+
// and lets us stay compatible with older versions of Rust
148+
let mutation_context: MutationContext<'static, '_> =
149+
::core::mem::transmute(context.mutation_context());
150+
let root: <R as RootProvider<'static>>::Root = f(mutation_context);
151+
Arena {
152+
context: context,
153+
root: ::core::mem::ManuallyDrop::new(root),
148154
}
155+
}
156+
}
149157

150-
/// The primary means of interacting with a garbage collected arena. Accepts a callback
151-
/// which receives a `MutationContext` and a reference to the root, and can return any
152-
/// non garbage collected value. The callback may "mutate" any part of the object graph
153-
/// during this call, but no garbage collection will take place during this method.
154-
#[allow(unused)]
155-
#[inline]
156-
pub fn mutate<F, R>(&self, f: F) -> R
157-
where
158-
F: for<'gc> FnOnce($crate::MutationContext<'gc, '_>, &$root<'gc>) -> R,
159-
{
160-
unsafe {
161-
f(
162-
self.context.mutation_context(),
163-
::std::mem::transmute::<&$root<'static>, _>(&*self.root),
164-
)
165-
}
166-
}
158+
/// Similar to `new`, but allows for constructor that can fail.
159+
#[allow(unused)]
160+
pub fn try_new<F, E>(arena_parameters: crate::ArenaParameters, f: F) -> Result<Arena<R>, E>
161+
where
162+
F: for<'gc> FnOnce(
163+
crate::MutationContext<'gc, '_>,
164+
) -> Result<<R as RootProvider<'gc>>::Root, E>,
165+
{
166+
unsafe {
167+
let context = crate::Context::new(arena_parameters);
168+
let mutation_context: MutationContext<'static, '_> =
169+
::core::mem::transmute(context.mutation_context());
170+
let root: <R as RootProvider<'static>>::Root = f(mutation_context)?;
171+
Ok(Arena {
172+
context: context,
173+
root: ::core::mem::ManuallyDrop::new(root),
174+
})
175+
}
176+
}
167177

168-
/// Return total currently used memory
169-
#[allow(unused)]
170-
#[inline]
171-
pub fn total_allocated(&self) -> usize {
172-
self.context.total_allocated()
173-
}
178+
/// The primary means of interacting with a garbage collected arena. Accepts a callback
179+
/// which receives a `MutationContext` and a reference to the root, and can return any
180+
/// non garbage collected value. The callback may "mutate" any part of the object graph
181+
/// during this call, but no garbage collection will take place during this method.
182+
#[allow(unused)]
183+
#[inline]
184+
pub fn mutate<F, T>(&self, f: F) -> T
185+
where
186+
F: for<'gc> FnOnce(crate::MutationContext<'gc, '_>, &<R as RootProvider<'gc>>::Root) -> T,
187+
{
188+
unsafe {
189+
f(
190+
self.context.mutation_context(),
191+
::core::mem::transmute::<&<R as RootProvider<'static>>::Root, _>(&*self.root),
192+
)
193+
}
194+
}
174195

175-
/// When the garbage collector is not sleeping, all allocated objects cause the arena to
176-
/// accumulate "allocation debt". This debt is then be used to time incremental garbage
177-
/// collection based on the tuning parameters set in `ArenaParameters`. The allocation
178-
/// debt is measured in bytes, but will generally increase at a rate faster than that of
179-
/// allocation so that collection will always complete.
180-
#[allow(unused)]
181-
#[inline]
182-
pub fn allocation_debt(&self) -> f64 {
183-
self.context.allocation_debt()
184-
}
196+
/// Return total currently used memory
197+
#[allow(unused)]
198+
#[inline]
199+
pub fn total_allocated(&self) -> usize {
200+
self.context.total_allocated()
201+
}
185202

186-
/// Run the incremental garbage collector until the allocation debt is <= 0.0. There is
187-
/// no minimum unit of work enforced here, so it may be faster to only call this method
188-
/// when the allocation debt is above some threshold.
189-
#[allow(unused)]
190-
#[inline]
191-
pub fn collect_debt(&mut self) {
192-
unsafe {
193-
let debt = self.context.allocation_debt();
194-
if debt > 0.0 {
195-
self.context.do_collection(&*self.root, debt);
196-
}
197-
}
198-
}
203+
/// When the garbage collector is not sleeping, all allocated objects cause the arena to
204+
/// accumulate "allocation debt". This debt is then be used to time incremental garbage
205+
/// collection based on the tuning parameters set in `ArenaParameters`. The allocation
206+
/// debt is measured in bytes, but will generally increase at a rate faster than that of
207+
/// allocation so that collection will always complete.
208+
#[allow(unused)]
209+
#[inline]
210+
pub fn allocation_debt(&self) -> f64 {
211+
self.context.allocation_debt()
212+
}
199213

200-
/// Run the current garbage collection cycle to completion, stopping once the garbage
201-
/// collector has entered the sleeping phase. If the garbage collector is currently
202-
/// sleeping, starts a new cycle and runs that cycle to completion.
203-
#[allow(unused)]
204-
pub fn collect_all(&mut self) {
205-
self.context.wake();
206-
unsafe {
207-
self.context
208-
.do_collection(&*self.root, ::std::f64::INFINITY);
209-
}
214+
/// Run the incremental garbage collector until the allocation debt is <= 0.0. There is
215+
/// no minimum unit of work enforced here, so it may be faster to only call this method
216+
/// when the allocation debt is above some threshold.
217+
#[allow(unused)]
218+
#[inline]
219+
pub fn collect_debt(&mut self) {
220+
unsafe {
221+
let debt = self.context.allocation_debt();
222+
if debt > 0.0 {
223+
self.context.do_collection(&*self.root, debt);
210224
}
211225
}
226+
}
212227

213-
impl Drop for $arena {
214-
fn drop(&mut self) {
215-
unsafe {
216-
::core::mem::ManuallyDrop::drop(&mut self.root);
217-
}
218-
}
228+
/// Run the current garbage collection cycle to completion, stopping once the garbage
229+
/// collector has entered the sleeping phase. If the garbage collector is currently
230+
/// sleeping, starts a new cycle and runs that cycle to completion.
231+
#[allow(unused)]
232+
pub fn collect_all(&mut self) {
233+
self.context.wake();
234+
unsafe {
235+
self.context
236+
.do_collection(&*self.root, ::core::f64::INFINITY);
219237
}
220-
};
238+
}
239+
}
240+
241+
impl<R: for<'a> RootProvider<'a> + ?Sized> Drop for Arena<R> {
242+
fn drop(&mut self) {
243+
unsafe {
244+
::core::mem::ManuallyDrop::drop(&mut self.root);
245+
}
246+
}
221247
}
222248

223249
/// Create a temporary arena without a root object and perform the given operation on it. No

src/gc-arena/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ mod static_collect;
2121
mod types;
2222

2323
pub use self::{
24-
arena::{rootless_arena, ArenaParameters},
24+
arena::{rootless_arena, Arena, ArenaParameters, RootProvider},
2525
collect::Collect,
2626
context::{CollectionContext, Context, MutationContext},
2727
gc::Gc,

0 commit comments

Comments
 (0)