Skip to main content

lua_types/
gc.rs

1//! `GcRef<T>` — the GC-managed reference handle.
2//!
3//! Phase A/B/C: thin newtype around `Rc<T>`.
4//! Phase D-1e (current): newtype around `lua_gc::Gc<T>` — Copy under the hood,
5//! tracks allocation in the active `Heap` (via `lua_gc::with_current_heap(...)`).
6//!
7//! Surface kept stable across the swap: `new`, `ptr_eq`, `identity`,
8//! `strong_count`, `weak_count`, `downgrade`. Existing code touching
9//! `gc.0` continues to work — `.0` is now `Gc<T>` instead of `Rc<T>`.
10//!
11//! # Weak refs (D-1)
12//!
13//! `GcWeak<T>` is currently a no-op wrapper: `upgrade` always returns
14//! `Some`, `strong_count` always returns `1`. Real weak semantics arrive
15//! in D-2 when the heap learns to mark weak refs separately. For D-1, weak
16//! tables ARE a known semantic gap (see PHASE_D_PLAN.md "Locked Decisions").
17
18use lua_gc::{Gc, Marker, Trace};
19
20/// A GC-managed pointer to a Lua collectable object. Newtype over
21/// `lua_gc::Gc<T>` so callers preserve `gc.0`-shape access while the
22/// backend swaps under them.
23#[derive(Debug)]
24pub struct GcRef<T: Trace + 'static>(pub Gc<T>);
25
26impl<T: Trace + 'static> GcRef<T> {
27    /// Allocate a new GC-tracked value. If a `HeapGuard` is active (set by
28    /// `state.run()` / `pcall_k`), the new allocation joins that heap's
29    /// allgc chain. Otherwise it allocates "uncollected" — leaks until
30    /// process exit, same as the old `Rc::new` behavior.
31    pub fn new(value: T) -> Self {
32        let gc = lua_gc::with_current_heap(|heap| match heap {
33            Some(heap) => heap.allocate(value),
34            None => Gc::new_uncollected(value),
35        });
36        GcRef(gc)
37    }
38
39    /// Cycle-aware trace dispatch.
40    ///
41    /// During D-0/D-1 (before a real Color::Gray flag is reachable from
42    /// inside Trace impls), `Marker::try_visit` records the underlying
43    /// allocation's identity and short-circuits a second recursion. Once
44    /// D-2 ships the in-header color flag, this helper collapses to
45    /// `m.mark(self.0)`.
46    pub fn trace_obj(&self, m: &mut Marker) {
47        if m.try_visit(self.identity()) {
48            (**self).trace(m);
49        }
50    }
51}
52
53impl<T: Trace + 'static> GcRef<T> {
54    /// Two `GcRef`s are identity-equal iff they point at the same box.
55    pub fn ptr_eq(a: &Self, b: &Self) -> bool {
56        Gc::ptr_eq(a.0, b.0)
57    }
58
59    /// Identity as a usize — used as a HashMap key for "same object" tests.
60    pub fn identity(&self) -> usize {
61        self.0.identity()
62    }
63
64    /// Number of strong references. Phase D-1: always returns 1 (no
65    /// refcount semantics). Real value once weak refs land in D-2.
66    pub fn strong_count(&self) -> usize {
67        1
68    }
69
70    /// Number of weak references. Phase D-1: always returns 0.
71    pub fn weak_count(&self) -> usize {
72        0
73    }
74
75    /// Get a weak handle. Phase D-1: GcWeak is a thin wrapper that always
76    /// upgrades; real weak semantics arrive in D-2.
77    pub fn downgrade(&self) -> GcWeak<T> {
78        GcWeak(self.0)
79    }
80
81    /// Charge (`delta > 0`) or refund (`delta < 0`) bytes of this object's
82    /// owned heap buffers against the active heap's pacer, so collections
83    /// fire at honest memory pressure. No-op on `delta == 0`, when no heap is
84    /// active, or when the underlying box is uncollected (see
85    /// [`lua_gc::Gc::account_buffer`]).
86    pub fn account_buffer(&self, delta: isize) {
87        if delta == 0 {
88            return;
89        }
90        lua_gc::with_current_heap(|h| {
91            if let Some(h) = h {
92                self.0.account_buffer(h, delta)
93            }
94        })
95    }
96}
97
98/// A weak handle to a `GcRef<T>`. Phase D-1 placeholder; D-2 will give
99/// this real semantics (None once the referent is swept).
100#[derive(Debug)]
101pub struct GcWeak<T: Trace + 'static>(pub Gc<T>);
102
103impl<T: Trace + 'static> GcWeak<T> {
104    /// Try to promote to a strong reference. Phase D-1: always Some
105    /// (weak semantics are not yet implemented).
106    pub fn upgrade(&self) -> Option<GcRef<T>> {
107        Some(GcRef(self.0))
108    }
109
110    /// Strong reference count of the target. Phase D-1: always 1.
111    pub fn strong_count(&self) -> usize {
112        1
113    }
114}
115
116impl<T: Trace + 'static> Clone for GcWeak<T> {
117    fn clone(&self) -> Self {
118        GcWeak(self.0)
119    }
120}
121
122impl<T: Trace + 'static> Clone for GcRef<T> {
123    fn clone(&self) -> Self {
124        GcRef(self.0)
125    }
126}
127
128impl<T: Trace + 'static> Copy for GcRef<T> {}
129
130impl<T: Trace + 'static> std::ops::Deref for GcRef<T> {
131    type Target = T;
132    fn deref(&self) -> &T {
133        &*self.0
134    }
135}
136
137impl<T: Trace + 'static> AsRef<T> for GcRef<T> {
138    fn as_ref(&self) -> &T {
139        &*self.0
140    }
141}
142
143impl<T: PartialEq + Trace + 'static> PartialEq for GcRef<T> {
144    fn eq(&self, other: &Self) -> bool {
145        Gc::ptr_eq(self.0, other.0) || **self == **other
146    }
147}
148
149// ──────────────────────────────────────────────────────────────────────────────
150// PORT STATUS
151//   source:        n/a (GcRef public wrapper around lua-gc::Gc<T>)
152//   target_crate:  lua-types
153//   confidence:    high
154//   todos:         0
155//   port_notes:    0
156//   unsafe_blocks: 0
157//   notes:         Thin wrapper type so consumers across crates don't depend on lua-gc's
158//                  raw Gc<T>. Clone/Deref/PartialEq forwarded; no unsafe surface.
159// ──────────────────────────────────────────────────────────────────────────────