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}