Skip to main content

gc_lang/
error.rs

1//! The error type returned by the fallible allocation path.
2
3use core::fmt;
4
5/// The reason a value could not be allocated into a [`Heap`](crate::Heap).
6///
7/// The heap addresses its slots with a 32-bit index, so it can hold up to
8/// `u32::MAX + 1` distinct slots over its lifetime. Slots are reused as objects
9/// are reclaimed, so this ceiling counts *simultaneously live plus never-yet-freed*
10/// slots, not total allocations — a program that allocates and collects in a steady
11/// loop never approaches it. Reaching the ceiling is the one recoverable failure an
12/// allocation can hit; [`Heap::try_alloc`] reports it through this type instead of
13/// aborting, so a runtime driving the heap from untrusted input can fail cleanly
14/// rather than crash.
15///
16/// The enum is `#[non_exhaustive]`: a later phase may add a second failure mode
17/// (for example, a per-heap byte budget), and a `match` on this type must already
18/// account for it.
19///
20/// [`Heap::try_alloc`]: crate::Heap::try_alloc
21///
22/// # Examples
23///
24/// ```
25/// use gc_lang::{GcError, Heap, Trace, Tracer};
26///
27/// struct Leaf;
28/// impl Trace for Leaf {
29///     fn trace(&self, _: &mut Tracer<'_>) {}
30/// }
31///
32/// // The fallible path returns this type; the happy path yields a handle.
33/// let mut heap: Heap<Leaf> = Heap::new();
34/// let handle = heap.try_alloc(Leaf).expect("the first slot is always available");
35/// assert!(heap.get(handle).is_some());
36/// # let _ = GcError::CapacityExhausted;
37/// ```
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39#[non_exhaustive]
40pub enum GcError {
41    /// The heap's slot space is full: it already addresses `u32::MAX + 1` slots
42    /// and cannot represent another handle.
43    ///
44    /// This is unreachable for any realistic workload — it takes more than four
45    /// billion slots that were never reclaimed — but it is reported rather than
46    /// ignored so the limit is a defined boundary, never a silent wrap. When it
47    /// does occur, run a collection to reclaim dead slots before allocating again.
48    CapacityExhausted,
49}
50
51impl fmt::Display for GcError {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        match *self {
54            Self::CapacityExhausted => {
55                f.write_str("heap is full: cannot address beyond u32::MAX slots")
56            }
57        }
58    }
59}
60
61impl core::error::Error for GcError {}
62
63#[cfg(test)]
64mod tests {
65    #![allow(clippy::unwrap_used, clippy::expect_used)]
66
67    extern crate alloc;
68    use alloc::string::ToString;
69
70    use super::*;
71
72    #[test]
73    fn test_capacity_exhausted_display_is_actionable() {
74        let text = GcError::CapacityExhausted.to_string();
75        assert!(text.contains("u32::MAX"), "{text}");
76    }
77
78    #[test]
79    fn test_error_is_copy_and_equatable() {
80        let a = GcError::CapacityExhausted;
81        let b = a;
82        assert_eq!(a, b);
83    }
84}