Skip to main content

runmat_gc_api/
handle.rs

1use std::fmt;
2use std::hash::{Hash, Hasher};
3use std::marker::PhantomData;
4use std::ptr::NonNull;
5
6/// Opaque handle token for a RunMat GC allocation.
7///
8/// `GcHandle` is intentionally not `Send` or `Sync`; a handle does not prove that
9/// the pointed-to object can be accessed safely from another thread.
10///
11/// ```compile_fail
12/// fn assert_send<T: Send>() {}
13/// assert_send::<runmat_gc_api::GcHandle>();
14/// ```
15///
16/// ```compile_fail
17/// fn assert_sync<T: Sync>() {}
18/// assert_sync::<runmat_gc_api::GcHandle>();
19/// ```
20///
21/// ```compile_fail
22/// fn deref(handle: runmat_gc_api::GcHandle) {
23///     let _ = *handle;
24/// }
25/// ```
26///
27/// ```compile_fail
28/// use std::ops::Deref;
29///
30/// fn deref_method(handle: runmat_gc_api::GcHandle) {
31///     let _ = handle.deref();
32/// }
33/// ```
34///
35/// ```compile_fail
36/// fn mutable_reference(mut handle: runmat_gc_api::GcHandle) {
37///     let _: &mut _ = &mut *handle;
38/// }
39/// ```
40#[derive(Copy, Clone)]
41pub struct GcHandle {
42    raw: NonNull<()>,
43    epoch: usize,
44    _not_send_sync: PhantomData<*const ()>,
45}
46
47impl GcHandle {
48    /// # Safety
49    ///
50    /// `raw` must point to a live RunMat GC allocation. This is an unchecked
51    /// bridge for `runmat-gc`; callers must not fabricate handles from ordinary
52    /// Rust allocations or stale addresses.
53    #[doc(hidden)]
54    pub unsafe fn from_ptr_unchecked(raw: NonNull<()>) -> Self {
55        Self {
56            raw,
57            epoch: 0,
58            _not_send_sync: PhantomData,
59        }
60    }
61
62    /// # Safety
63    ///
64    /// `raw` and `epoch` must identify a currently live RunMat GC allocation.
65    /// This is an unchecked bridge for `runmat-gc`; callers must not fabricate
66    /// handles from ordinary Rust allocations or stale allocation epochs.
67    #[doc(hidden)]
68    pub unsafe fn from_parts_unchecked(raw: NonNull<()>, epoch: usize) -> Self {
69        Self {
70            raw,
71            epoch,
72            _not_send_sync: PhantomData,
73        }
74    }
75
76    pub fn addr(&self) -> usize {
77        self.raw.as_ptr() as usize
78    }
79
80    #[doc(hidden)]
81    pub fn epoch(&self) -> usize {
82        self.epoch
83    }
84
85    /// # Safety
86    ///
87    /// The returned pointer may only be dereferenced by code that can prove the
88    /// handle is still owned by the RunMat GC heap and that aliasing rules are
89    /// upheld for the intended access.
90    #[doc(hidden)]
91    pub unsafe fn as_ptr_unchecked(&self) -> NonNull<()> {
92        self.raw
93    }
94}
95
96impl PartialEq for GcHandle {
97    fn eq(&self, other: &Self) -> bool {
98        self.raw == other.raw && self.epoch == other.epoch
99    }
100}
101
102impl Eq for GcHandle {}
103
104impl Hash for GcHandle {
105    fn hash<H: Hasher>(&self, state: &mut H) {
106        self.raw.hash(state);
107        self.epoch.hash(state)
108    }
109}
110
111impl fmt::Debug for GcHandle {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "GcHandle({:p}@{})", self.raw.as_ptr(), self.epoch)
114    }
115}
116
117impl fmt::Display for GcHandle {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        write!(f, "{:p}", self.raw.as_ptr())
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::GcHandle;
126    use std::collections::hash_map::DefaultHasher;
127    use std::hash::{Hash, Hasher};
128    use std::ptr::NonNull;
129
130    #[test]
131    fn equality_is_pointer_identity_not_pointee_value() {
132        let raw_a = Box::into_raw(Box::new(7_u64));
133        let raw_b = Box::into_raw(Box::new(7_u64));
134        let ptr_a = unsafe {
135            GcHandle::from_ptr_unchecked(NonNull::new(raw_a.cast()).expect("non-null test pointer"))
136        };
137        let ptr_b = unsafe {
138            GcHandle::from_ptr_unchecked(NonNull::new(raw_b.cast()).expect("non-null test pointer"))
139        };
140
141        assert_eq!(ptr_a, ptr_a);
142        assert_ne!(ptr_a, ptr_b);
143
144        unsafe {
145            drop(Box::from_raw(raw_a));
146            drop(Box::from_raw(raw_b));
147        }
148    }
149
150    #[test]
151    fn hash_includes_pointer_and_epoch_identity() {
152        let raw_a = Box::into_raw(Box::new(7_u64));
153        let raw_b = Box::into_raw(Box::new(7_u64));
154        let ptr_a = unsafe {
155            GcHandle::from_parts_unchecked(
156                NonNull::new(raw_a.cast()).expect("non-null test pointer"),
157                1,
158            )
159        };
160        let ptr_a_next_epoch = unsafe {
161            GcHandle::from_parts_unchecked(
162                NonNull::new(raw_a.cast()).expect("non-null test pointer"),
163                2,
164            )
165        };
166        let ptr_b = unsafe {
167            GcHandle::from_parts_unchecked(
168                NonNull::new(raw_b.cast()).expect("non-null test pointer"),
169                1,
170            )
171        };
172
173        let mut ptr_hash = DefaultHasher::new();
174        ptr_a.hash(&mut ptr_hash);
175        let mut same_hash = DefaultHasher::new();
176        ptr_a.hash(&mut same_hash);
177        let mut next_epoch_hash = DefaultHasher::new();
178        ptr_a_next_epoch.hash(&mut next_epoch_hash);
179
180        assert_eq!(ptr_hash.finish(), same_hash.finish());
181        assert_ne!(ptr_hash.finish(), next_epoch_hash.finish());
182        assert_ne!(ptr_a, ptr_a_next_epoch);
183        assert_ne!(ptr_a, ptr_b);
184
185        unsafe {
186            drop(Box::from_raw(raw_a));
187            drop(Box::from_raw(raw_b));
188        }
189    }
190
191    #[test]
192    fn debug_and_display_do_not_dereference_pointee() {
193        let raw = Box::into_raw(Box::new(7_u64));
194        let ptr = unsafe {
195            GcHandle::from_ptr_unchecked(NonNull::new(raw.cast()).expect("non-null test pointer"))
196        };
197
198        assert!(format!("{ptr:?}").starts_with("GcHandle(0x"));
199        assert!(ptr.to_string().starts_with("0x"));
200
201        unsafe {
202            drop(Box::from_raw(raw));
203        }
204    }
205}