Skip to content

Instantly share code, notes, and snippets.

@aapoalas
Last active August 11, 2024 14:13
Show Gist options
  • Select an option

  • Save aapoalas/ae65b811490820dad061da1817e94c28 to your computer and use it in GitHub Desktop.

Select an option

Save aapoalas/ae65b811490820dad061da1817e94c28 to your computer and use it in GitHub Desktop.
Rust GC lifetime management
/// Some "heap memory arena" of sorts. It holds within it
/// vectors of things. "Referencing" into the arena is done
/// using Value structs.
struct Arena {
data: AtomicUsize,
}
/// A calling context where garbage collection can happen.
struct GcContext<'gc> {
marker: PhantomData<&'gc mut ()>,
arena: *const Arena,
}
/// A calling context where no garbage collection can happen.
struct NoGcContext<'gc> {
marker: PhantomData<&'gc ()>,
arena: *const Arena,
}
struct Value<'gc> {
/// A marker to hold a "borrow" on an index in the Arena.
/// Conceptually 'gc lifetime is always smaller than the
/// Arena's lifetime, but we skip that part here.
/// The important thing is that GC can change indexes in
/// the Arena. Thus it is not safe to hold onto a Value<'gc>
/// when GC happens.
marker: PhantomData<&'gc ()>,
/// Some index into some vector in Arena.
data: usize,
}
/// Call in a GC context: Calling this can trigger GC, which will invalidate all 'gc
/// tagged Values. Holding onto a Value across a possible GC point must be done via
/// "rooting".
fn call_1<'gc>(
context: GcContext<'gc>,
value_1: Value<'gc>,
value_2: Value<'gc>,
) {
let data: usize = value_2.get_data(context);
let value_3: Value<'_> = call_2(context, value_1);
// OK! value_3 is after potential GC, data is just usize.
println!("Value 3 data: {}", value_3.get_data(context));
println!("Value 2 data: {}", data);
// NOT OK! value_1's lifetime got "consumed" by call_2
// println!("Value 1 data: {}", value_1.get_data(context));
{
let context: NoGcContext<'_> = context.no_gc();
call_3(context, value_3);
// OK! call_3 does not "consume" 'gc lifetime
println!("Value 3 data: {}", value_3.get_data(context));
println!("Value 2 data: {}", data);
}
}
/// A call in a GC context that does trigger GC.
fn call_2<'gc>(
context: GcContext<'gc>,
value_1: Value<'gc>,
) -> Value<'gc> {
let value_root = value_1.root(context);
context.gc();
value_root.unroot(context)
}
/// A call in a no-GC context: Holding onto Values while you call this
/// is safe as this cannot invalidate them.
fn call_3<'gc>(
context: NoGcContext<'gc>,
value_1: Value<'gc>,
) {
// do something in a no-GC context
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment