Skip to main content

asupersync/types/
rref.rs

1//! Region-owned reference type.
2//!
3//! `RRef` provides a way for migrating (`Send`) tasks to reference data
4//! allocated in the region heap safely.
5//!
6//! # Design
7//!
8//! `RRef<T>` is a smart reference that:
9//! - Stores a `RegionId` and `HeapIndex` for runtime lookup
10//! - Is `Send + Sync` when `T: Send + Sync`
11//! - Requires passing the `RegionRecord` to access the underlying value
12//! - Validates region and allocation validity at access time
13//!
14//! The key invariant is that region heap allocations remain valid for all
15//! tasks owned by the region. Since tasks cannot outlive their owning region
16//! (structured concurrency guarantee), `RRef`s held by tasks are always valid
17//! while those tasks are running.
18//!
19//! # Example
20//!
21//! ```ignore
22//! // In region context
23//! let data = region.heap_alloc(vec![1, 2, 3]).expect("heap alloc");
24//! let rref = RRef::<Vec<i32>>::new(region_id, data);
25//!
26//! // Pass to spawned task
27//! spawn(async move {
28//!     // Access via region reference
29//!     let value = rref.get(&runtime_state, region_id)?;
30//!     println!("data: {:?}", value);
31//! });
32//! ```
33//!
34//! # Safety
35//!
36//! This type uses no unsafe code. Safety is enforced through:
37//! 1. Structured concurrency: tasks cannot outlive their region
38//! 2. Runtime validation: access checks region/index validity
39//! 3. Type safety: HeapIndex includes TypeId for type checking
40
41use crate::runtime::region_heap::HeapIndex;
42use crate::types::RegionId;
43use std::fmt;
44use std::hash::{Hash, Hasher};
45use std::marker::PhantomData;
46
47/// A region-owned reference to heap-allocated data.
48///
49/// `RRef<T>` allows tasks to hold references to data allocated in a region's
50/// heap. The reference is valid as long as the owning region is open.
51///
52/// # Send/Sync
53///
54/// `RRef<T>` is `Send` when `T: Send` and `Sync` when `T: Sync`. This allows
55/// `RRef`s to be safely passed to worker threads. The bounds are automatically
56/// provided through `PhantomData<T>` - no unsafe code required.
57///
58/// # Cloning
59///
60/// `RRef` is `Clone + Copy` because it contains only indices, not the actual
61/// data. Multiple `RRef`s can point to the same heap allocation.
62pub struct RRef<T> {
63    /// The region that owns this allocation.
64    region_id: RegionId,
65    /// Index into the region's heap.
66    index: HeapIndex,
67    /// Marker for the referenced type.
68    ///
69    /// Using `PhantomData<T>` ensures `RRef<T>` is:
70    /// - Send when T: Send
71    /// - Sync when T: Sync
72    ///
73    /// This is safe because RRef contains only indices (Copy types), not the
74    /// actual data. Access to the data goes through the RegionHeap which has
75    /// its own synchronization (RwLock).
76    _marker: PhantomData<T>,
77}
78
79// Manual Clone impl to avoid requiring T: Clone (RRef is Copy regardless of T)
80impl<T> Clone for RRef<T> {
81    #[inline]
82    fn clone(&self) -> Self {
83        *self
84    }
85}
86
87impl<T> Copy for RRef<T> {}
88
89impl<T> fmt::Debug for RRef<T> {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.debug_struct("RRef")
92            .field("region_id", &self.region_id)
93            .field("index", &self.index)
94            .finish()
95    }
96}
97
98impl<T> PartialEq for RRef<T> {
99    #[inline]
100    fn eq(&self, other: &Self) -> bool {
101        self.region_id == other.region_id && self.index == other.index
102    }
103}
104
105impl<T> Eq for RRef<T> {}
106
107impl<T> Hash for RRef<T> {
108    #[inline]
109    fn hash<H: Hasher>(&self, state: &mut H) {
110        self.region_id.hash(state);
111        self.index.hash(state);
112    }
113}
114
115// Send and Sync are automatically derived via PhantomData<T>:
116// - RRef<T>: Send when T: Send (PhantomData<T>: Send when T: Send)
117// - RRef<T>: Sync when T: Sync (PhantomData<T>: Sync when T: Sync)
118//
119// This is safe because:
120// - RRef contains only indices (Copy types)
121// - The actual data is in RegionHeap which has its own synchronization (RwLock)
122// - Access requires going through the heap which has proper locking
123
124// Accessor methods available for all RRef<T> regardless of bounds.
125// These just return stored indices (Copy types) and don't access the underlying data.
126impl<T> RRef<T> {
127    /// Returns the region ID that owns this reference.
128    #[inline]
129    #[must_use]
130    pub const fn region_id(&self) -> RegionId {
131        self.region_id
132    }
133
134    /// Returns the underlying heap index.
135    #[inline]
136    #[must_use]
137    pub const fn heap_index(&self) -> HeapIndex {
138        self.index
139    }
140}
141
142// Construction requires Send + Sync + 'static for soundness when used with Send tasks.
143impl<T: Send + Sync + 'static> RRef<T> {
144    /// Creates a new region reference from a region ID and heap index.
145    ///
146    /// # Arguments
147    ///
148    /// * `region_id` - The ID of the region that owns the allocation
149    /// * `index` - The heap index returned from `heap_alloc`
150    ///
151    /// # Bounds
152    ///
153    /// The `Send + Sync + 'static` bounds on `T` ensure that:
154    /// - The referenced data can be safely shared across threads
155    /// - The `RRef` can be passed to Send tasks that may migrate
156    ///
157    /// # Example
158    ///
159    /// ```ignore
160    /// let index = region.heap_alloc(42u32).expect("heap alloc");
161    /// let rref = RRef::new(region_id, index);
162    /// ```
163    #[inline]
164    #[must_use]
165    pub const fn new(region_id: RegionId, index: HeapIndex) -> Self {
166        Self {
167            region_id,
168            index,
169            _marker: PhantomData,
170        }
171    }
172}
173
174/// Error returned when accessing an RRef fails.
175#[derive(Debug, Clone, PartialEq, Eq)]
176pub enum RRefError {
177    /// The region does not exist in the runtime state.
178    RegionNotFound(RegionId),
179    /// The heap allocation is no longer valid (deallocated or type mismatch).
180    AllocationInvalid,
181    /// The region ID in the RRef doesn't match the provided region.
182    RegionMismatch {
183        /// The region ID stored in the RRef.
184        expected: RegionId,
185        /// The region ID that was provided.
186        actual: RegionId,
187    },
188    /// The region is closed and its heap has been reclaimed.
189    RegionClosed,
190    /// The access witness references a different region than expected.
191    WrongRegion,
192}
193
194impl fmt::Display for RRefError {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        match self {
197            Self::RegionNotFound(id) => write!(f, "region not found: {id:?}"),
198            Self::AllocationInvalid => write!(f, "heap allocation is invalid"),
199            Self::RegionMismatch { expected, actual } => {
200                write!(f, "region mismatch: expected {expected:?}, got {actual:?}")
201            }
202            Self::RegionClosed => write!(f, "region is closed"),
203            Self::WrongRegion => write!(f, "access witness references wrong region"),
204        }
205    }
206}
207
208impl std::error::Error for RRefError {}
209
210/// Capability witness proving access rights to a specific region's heap.
211///
212/// Constructed exclusively by [`RegionRecord::access_witness`] when the region
213/// is in a non-terminal state. External code cannot forge a witness because
214/// the constructor is `pub(crate)`.
215///
216/// # Usage
217///
218/// ```ignore
219/// let witness = region_record.access_witness()?;
220/// let value = region_record.rref_get_with(&rref, witness)?;
221/// ```
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub struct RRefAccessWitness {
224    region_id: RegionId,
225}
226
227impl RRefAccessWitness {
228    /// Creates a new access witness for the given region.
229    ///
230    /// This is `pub(crate)` to prevent external forging. Use
231    /// [`RegionRecord::access_witness`] to obtain a witness.
232    #[must_use]
233    pub(crate) const fn new(region_id: RegionId) -> Self {
234        Self { region_id }
235    }
236
237    /// Returns the region this witness grants access to.
238    #[must_use]
239    pub const fn region(&self) -> RegionId {
240        self.region_id
241    }
242}
243
244impl<T> RRef<T> {
245    /// Validates that a witness matches this RRef's region.
246    ///
247    /// Returns `Err(WrongRegion)` if the witness was obtained from a different
248    /// region than the one this RRef belongs to.
249    pub fn validate_witness(&self, witness: &RRefAccessWitness) -> Result<(), RRefError> {
250        if witness.region() != self.region_id {
251            return Err(RRefError::WrongRegion);
252        }
253        Ok(())
254    }
255}
256
257/// Extension trait for accessing RRef values through a region.
258///
259/// This trait is implemented for types that can provide access to a region's heap.
260/// Implementations must validate region ownership and state before returning data.
261pub trait RRefAccess {
262    /// Gets a clone of the value referenced by an RRef.
263    ///
264    /// Returns an error if the region doesn't match or the allocation is invalid.
265    fn rref_get<T: Clone + 'static>(&self, rref: &RRef<T>) -> Result<T, RRefError>;
266
267    /// Executes a closure with a reference to the value.
268    ///
269    /// This is more efficient than `rref_get` when you don't need to clone.
270    fn rref_with<T: 'static, R, F: FnOnce(&T) -> R>(
271        &self,
272        rref: &RRef<T>,
273        f: F,
274    ) -> Result<R, RRefError>;
275
276    /// Gets a clone of the value, requiring a pre-validated witness.
277    ///
278    /// The witness proves the caller has been granted access to the region.
279    /// This is the preferred access path in capability-aware code.
280    fn rref_get_with<T: Clone + 'static>(
281        &self,
282        rref: &RRef<T>,
283        witness: RRefAccessWitness,
284    ) -> Result<T, RRefError>;
285
286    /// Executes a closure with a reference, requiring a pre-validated witness.
287    ///
288    /// The witness proves the caller has been granted access to the region.
289    fn rref_with_witness<T: 'static, R, F: FnOnce(&T) -> R>(
290        &self,
291        rref: &RRef<T>,
292        witness: RRefAccessWitness,
293        f: F,
294    ) -> Result<R, RRefError>;
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use crate::record::RegionRecord;
301    use crate::types::Budget;
302    use crate::util::ArenaIndex;
303
304    fn test_region_id() -> RegionId {
305        RegionId::from_arena(ArenaIndex::new(0, 0))
306    }
307
308    #[test]
309    fn rref_is_copy_and_clone() {
310        let region_id = test_region_id();
311        let record = RegionRecord::new(region_id, None, Budget::INFINITE);
312        let index = record.heap_alloc(42u32).expect("heap alloc");
313        let rref = RRef::<u32>::new(region_id, index);
314
315        // Test Copy
316        let rref2 = rref;
317        assert_eq!(rref.region_id(), rref2.region_id());
318
319        // Clone is implied by Copy, assert the trait bound explicitly.
320        assert_clone::<RRef<u32>>();
321    }
322
323    #[test]
324    fn rref_equality() {
325        let region_id = test_region_id();
326        let record = RegionRecord::new(region_id, None, Budget::INFINITE);
327
328        let index1 = record.heap_alloc(1u32).expect("heap alloc");
329        let index2 = record.heap_alloc(2u32).expect("heap alloc");
330
331        let rref1a = RRef::<u32>::new(region_id, index1);
332        let rref1_clone = RRef::<u32>::new(region_id, index1);
333        let rref2 = RRef::<u32>::new(region_id, index2);
334
335        assert_eq!(rref1a, rref1_clone);
336        assert_ne!(rref1a, rref2);
337    }
338
339    #[test]
340    fn rref_accessors() {
341        let region_id = test_region_id();
342        let record = RegionRecord::new(region_id, None, Budget::INFINITE);
343        let index = record.heap_alloc("hello".to_string()).expect("heap alloc");
344        let rref = RRef::<String>::new(region_id, index);
345
346        assert_eq!(rref.region_id(), region_id);
347        assert_eq!(rref.heap_index(), index);
348    }
349
350    #[test]
351    fn rref_debug_format() {
352        let region_id = test_region_id();
353        let record = RegionRecord::new(region_id, None, Budget::INFINITE);
354        let index = record.heap_alloc(42u32).expect("heap alloc");
355        let rref = RRef::<u32>::new(region_id, index);
356
357        let debug_str = format!("{rref:?}");
358        assert!(debug_str.contains("RRef"));
359        assert!(debug_str.contains("region_id"));
360        assert!(debug_str.contains("index"));
361    }
362
363    #[test]
364    fn rref_access_through_region_record() {
365        let region_id = test_region_id();
366        let record = RegionRecord::new(region_id, None, Budget::INFINITE);
367        let index = record.heap_alloc("hello".to_string()).expect("heap alloc");
368        let rref = RRef::<String>::new(region_id, index);
369
370        let value = record.rref_get(&rref).expect("rref_get");
371        assert_eq!(value, "hello");
372
373        let len = record.rref_with(&rref, String::len).expect("rref_with");
374        assert_eq!(len, 5);
375    }
376
377    #[test]
378    fn rref_region_mismatch_is_error() {
379        let region_a = test_region_id();
380        let region_b = RegionId::from_arena(ArenaIndex::new(1, 0));
381        let record_a = RegionRecord::new(region_a, None, Budget::INFINITE);
382        let record_b = RegionRecord::new(region_b, None, Budget::INFINITE);
383
384        let index = record_a.heap_alloc(7u32).expect("heap alloc");
385        let rref = RRef::<u32>::new(region_a, index);
386
387        let err = record_b.rref_get(&rref).expect_err("region mismatch");
388        assert_eq!(
389            err,
390            RRefError::RegionMismatch {
391                expected: region_a,
392                actual: region_b,
393            }
394        );
395    }
396
397    // Compile-time test for Send/Sync bounds
398    fn assert_clone<T: Clone>() {}
399    fn assert_send<T: Send>() {}
400    fn assert_sync<T: Sync>() {}
401
402    #[test]
403    fn rref_send_sync_bounds() {
404        // RRef<T> is Send when T: Send
405        assert_send::<RRef<u32>>();
406        assert_send::<RRef<String>>();
407        assert_send::<RRef<Vec<i32>>>();
408
409        // RRef<T> is Sync when T: Sync
410        assert_sync::<RRef<u32>>();
411        assert_sync::<RRef<String>>();
412        assert_sync::<RRef<Vec<i32>>>();
413    }
414
415    // ================================================================
416    // Witness validation tests (bd-27c7l)
417    // ================================================================
418
419    #[test]
420    fn validate_witness_matching_region_succeeds() {
421        let rid = test_region_id();
422        let record = RegionRecord::new(rid, None, Budget::INFINITE);
423        let index = record.heap_alloc(42u32).expect("heap alloc");
424        let rref = RRef::<u32>::new(rid, index);
425
426        let witness = RRefAccessWitness::new(rid);
427        assert!(rref.validate_witness(&witness).is_ok());
428    }
429
430    #[test]
431    fn validate_witness_wrong_region_fails() {
432        let rid_a = test_region_id();
433        let rid_b = RegionId::from_arena(ArenaIndex::new(77, 0));
434        let record = RegionRecord::new(rid_a, None, Budget::INFINITE);
435        let index = record.heap_alloc(42u32).expect("heap alloc");
436        let rref = RRef::<u32>::new(rid_a, index);
437
438        let wrong_witness = RRefAccessWitness::new(rid_b);
439        let err = rref.validate_witness(&wrong_witness);
440        assert_eq!(err.unwrap_err(), RRefError::WrongRegion);
441    }
442
443    #[test]
444    fn access_witness_is_copy_and_eq() {
445        let rid = test_region_id();
446        let w1 = RRefAccessWitness::new(rid);
447        let w2 = w1; // Copy
448        assert_eq!(w1, w2);
449        assert_eq!(w1.region(), rid);
450    }
451
452    #[test]
453    fn rref_error_display_coverage() {
454        let rid = test_region_id();
455        let cases: Vec<(RRefError, &str)> = vec![
456            (RRefError::RegionNotFound(rid), "region not found"),
457            (RRefError::AllocationInvalid, "heap allocation is invalid"),
458            (
459                RRefError::RegionMismatch {
460                    expected: rid,
461                    actual: rid,
462                },
463                "region mismatch",
464            ),
465            (RRefError::RegionClosed, "region is closed"),
466            (
467                RRefError::WrongRegion,
468                "access witness references wrong region",
469            ),
470        ];
471
472        for (err, expected_substring) in cases {
473            let msg = format!("{err}");
474            assert!(
475                msg.contains(expected_substring),
476                "expected '{expected_substring}' in '{msg}'"
477            );
478        }
479    }
480}