Last active
August 11, 2024 14:13
-
-
Save aapoalas/ae65b811490820dad061da1817e94c28 to your computer and use it in GitHub Desktop.
Rust GC lifetime management
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// 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