Skip to main content

cel_runtime/
array.rs

1//! Concrete, contiguous ownership for homogeneous CEL arrays.
2//!
3//! Transfers to and from vectors preserve their allocations. Nested arrays use
4//! `Array<Array<T>>`, so each outer element is itself a typed array owner.
5//! Its outer allocation transfers to `Vec<Array<T>>`, not `Vec<Vec<T>>`;
6//! explicitly converting each inner owner builds a new outer vector. Borrowing
7//! exposes `&[Array<T>]` without consuming the outer or inner owners.
8//!
9//! ```
10//! use cel_runtime::Array;
11//!
12//! let rows = Array::from_vec(vec![Array::from_vec(vec![1_i32, 2])]);
13//! assert_eq!(rows.as_slice()[0].as_slice(), &[1, 2]);
14//! ```
15
16use std::fmt;
17use std::marker::PhantomData;
18use std::mem::ManuallyDrop;
19use std::ptr::NonNull;
20
21/// Stores an array's pointer, element count, and element capacity, in that order.
22///
23/// The pointer is non-null and aligned for the concrete element type, including empty and
24/// zero-sized arrays. Length and capacity count elements, not bytes; length does
25/// not exceed capacity. The first `len` elements are initialized. For non-zero-sized
26/// elements with nonzero capacity, the pointer uniquely owns a global-allocator
27/// allocation compatible with `Vec<T>` at exactly this capacity and element type.
28///
29/// The header owns its member destruction operation. Erased arrays additionally
30/// own an element descriptor; typed arrays bind concrete drop glue without one.
31/// This layout is independent of Rust's unspecified `Vec<T>` header layout.
32#[repr(C)]
33pub(crate) struct RawArray {
34    pub(crate) ptr: NonNull<()>,
35    pub(crate) len: usize,
36    pub(crate) capacity: usize,
37    drop_members: unsafe fn(&mut RawArray),
38    pub(crate) element_type: Option<crate::ValueType>,
39}
40
41impl RawArray {
42    /// Adopts an erased array allocation with its live element destruction metadata.
43    ///
44    /// # Safety
45    /// The raw parts must uniquely own `len` initialized elements of `element_type`,
46    /// using its layout and the global allocator at exactly `capacity`.
47    pub(crate) unsafe fn from_erased(
48        ptr: NonNull<()>,
49        len: usize,
50        capacity: usize,
51        element_type: crate::ValueType,
52    ) -> Self {
53        Self {
54            ptr,
55            len,
56            capacity,
57            drop_members: drop_erased_members,
58            element_type: Some(element_type),
59        }
60    }
61}
62
63impl Drop for RawArray {
64    /// Destroys live members in reverse order and releases their allocation.
65    ///
66    /// - Complexity: O(n) member destructions for n live members.
67    fn drop(&mut self) {
68        // SAFETY: Each constructor binds destruction to the allocation's exact layout.
69        unsafe { (self.drop_members)(self) };
70    }
71}
72
73/// Destroys concrete members and frees their original vector allocation.
74///
75/// # Safety
76/// The header must uniquely own the raw parts of a `Vec<T>`.
77/// - Complexity: O(n) member destructions for n live members.
78unsafe fn drop_typed_members<T>(raw: &mut RawArray) {
79    let mut guard = ReverseDrop(unsafe {
80        Vec::from_raw_parts(raw.ptr.cast::<T>().as_ptr(), raw.len, raw.capacity)
81    });
82    raw.len = 0;
83    while let Some(value) = guard.0.pop() {
84        drop(value);
85    }
86}
87
88/// Destroys erased members using metadata owned by their array header.
89///
90/// # Safety
91/// The header's descriptor must match every initialized element and its allocation.
92/// - Complexity: O(n) member destructions plus recursive member destruction.
93unsafe fn drop_erased_members(raw: &mut RawArray) {
94    let element = raw
95        .element_type
96        .as_ref()
97        .expect("erased element descriptor");
98    let layout = std::alloc::Layout::from_size_align(
99        element
100            .size()
101            .checked_mul(raw.capacity)
102            .expect("valid array capacity"),
103        element.align(),
104    )
105    .expect("valid array layout");
106    let _allocation = crate::owned_value::Allocation {
107        pointer: raw.ptr.cast(),
108        layout,
109    };
110    let mut prefix = crate::owned_value::Prefix {
111        base: raw.ptr.as_ptr().cast(),
112        children: crate::owned_value::Children::Elements(element),
113        initialized: raw.len,
114    };
115    raw.len = 0;
116    unsafe { prefix.destroy() };
117}
118
119/// Owns homogeneous, contiguous elements without bounds on the element type.
120///
121/// The transparent representation is an owning `RawArray` header, including
122/// member destruction dispatch. Its elements have the concrete layout of `T`.
123/// Nested arrays use `Array<Array<T>>`, not nested vectors.
124///
125/// Dropping an array destroys its elements exactly once in reverse index order,
126/// then releases its allocation. Remaining elements also receive reverse-order
127/// cleanup if a destructor unwinds; a second destructor panic during unwinding
128/// aborts as usual. After [`Self::into_vec`], the vector instead follows Rust's
129/// vector destruction behavior.
130///
131/// - Complexity: Destruction takes O(n) time and O(1) auxiliary space, excluding
132///   element destructor costs.
133///
134/// # Examples
135///
136/// ```
137/// use cel_runtime::Array;
138///
139/// let array = Array::from_vec(vec![String::from("leaf")]);
140/// assert_eq!(array.as_slice(), &["leaf"]);
141/// ```
142///
143/// Elements that are not `Send` cannot become transferable through an array:
144///
145/// ```compile_fail
146/// use cel_runtime::Array;
147/// use std::rc::Rc;
148///
149/// fn require_send<T: Send>() {}
150/// require_send::<Array<Rc<()>>>();
151/// ```
152///
153/// Elements that are not `Sync` cannot become shareable through an array:
154///
155/// ```compile_fail
156/// use cel_runtime::Array;
157/// use std::cell::Cell;
158///
159/// fn require_sync<T: Sync>() {}
160/// require_sync::<Array<Cell<i32>>>();
161/// ```
162#[repr(transparent)]
163pub struct Array<T> {
164    pub(crate) raw: RawArray,
165    marker: PhantomData<T>,
166}
167
168// SAFETY: The header uniquely owns T elements and never exposes untyped access
169// publicly. Moving ownership between threads is valid exactly when T is Send.
170unsafe impl<T: Send> Send for Array<T> {}
171// SAFETY: Shared access exposes only &[T], which is shareable exactly when T is Sync.
172unsafe impl<T: Sync> Sync for Array<T> {}
173
174impl<T> Array<T> {
175    /// Takes ownership of a vector without moving elements or allocating.
176    ///
177    /// - Postcondition: Contents, allocation pointer, length, and capacity are
178    ///   unchanged. Empty and zero-sized vectors retain their raw parts.
179    ///
180    /// # Examples
181    ///
182    /// ```
183    /// use cel_runtime::Array;
184    /// let values = vec![3_i32, 5];
185    /// let pointer = values.as_ptr();
186    /// let array = Array::from_vec(values);
187    /// assert_eq!(array.as_slice().as_ptr(), pointer);
188    /// ```
189    #[must_use]
190    pub fn from_vec(values: Vec<T>) -> Self {
191        let (ptr, len, capacity) = values.into_raw_parts();
192        Self {
193            raw: RawArray {
194                // SAFETY: Vec raw parts always provide a non-null aligned pointer.
195                ptr: unsafe { NonNull::new_unchecked(ptr.cast()) },
196                len,
197                capacity,
198                drop_members: drop_typed_members::<T>,
199                element_type: None,
200            },
201            marker: PhantomData,
202        }
203    }
204
205    /// Transfers ownership into a vector without moving elements or allocating.
206    ///
207    /// - Postcondition: Contents, allocation pointer, length, and capacity are
208    ///   unchanged; this array runs no element destructors.
209    ///
210    /// # Examples
211    ///
212    /// ```
213    /// use cel_runtime::Array;
214    /// let array = Array::from_vec(vec![3_i32, 5]);
215    /// let pointer = array.as_slice().as_ptr();
216    /// let values = array.into_vec();
217    /// assert_eq!(values.as_ptr(), pointer);
218    /// assert_eq!(values, [3, 5]);
219    /// ```
220    #[must_use]
221    pub fn into_vec(self) -> Vec<T> {
222        let mut owner = ManuallyDrop::new(self);
223        drop(owner.raw.element_type.take());
224        // SAFETY: The header preserves Vec<T>'s raw parts, and ManuallyDrop
225        // prevents the old owner from destroying the transferred allocation.
226        unsafe {
227            Vec::from_raw_parts(
228                owner.raw.ptr.cast::<T>().as_ptr(),
229                owner.raw.len,
230                owner.raw.capacity,
231            )
232        }
233    }
234
235    /// Borrows the contiguous elements without copying or allocating.
236    ///
237    /// # Examples
238    ///
239    /// ```
240    /// use cel_runtime::Array;
241    /// let array = Array::from_vec(vec![3_i32, 5]);
242    /// let values: &[i32] = array.as_slice();
243    /// assert_eq!(values, &[3, 5]);
244    /// ```
245    #[must_use]
246    pub fn as_slice(&self) -> &[T] {
247        // SAFETY: The typed owner guarantees an aligned non-null pointer and
248        // len initialized T elements, alive for the lifetime of this borrow.
249        unsafe { std::slice::from_raw_parts(self.raw.ptr.cast::<T>().as_ptr(), self.raw.len) }
250    }
251
252    /// Returns the number of initialized elements.
253    ///
254    /// # Examples
255    ///
256    /// ```
257    /// use cel_runtime::Array;
258    /// assert_eq!(Array::from_vec(vec![3_i32, 5]).len(), 2);
259    /// ```
260    #[must_use]
261    pub fn len(&self) -> usize {
262        self.raw.len
263    }
264
265    /// Returns the allocation capacity in elements, as reported by the source vector.
266    ///
267    /// Zero-sized elements retain the vector's capacity of `usize::MAX`.
268    ///
269    /// # Examples
270    ///
271    /// ```
272    /// use cel_runtime::Array;
273    /// let values = Vec::<i32>::with_capacity(8);
274    /// let capacity = values.capacity();
275    /// assert_eq!(Array::from_vec(values).capacity(), capacity);
276    /// ```
277    #[must_use]
278    pub fn capacity(&self) -> usize {
279        self.raw.capacity
280    }
281
282    /// Returns whether the array contains no elements.
283    ///
284    /// # Examples
285    ///
286    /// ```
287    /// use cel_runtime::Array;
288    /// assert!(Array::from_vec(Vec::<i32>::new()).is_empty());
289    /// ```
290    #[must_use]
291    pub fn is_empty(&self) -> bool {
292        self.raw.len == 0
293    }
294}
295
296/// Owns remaining elements during cleanup and frees storage even during unwinding.
297struct ReverseDrop<T>(Vec<T>);
298
299impl<T> Drop for ReverseDrop<T> {
300    /// Drops remaining elements in reverse order in O(n) time and O(1) space.
301    fn drop(&mut self) {
302        while let Some(value) = self.0.pop() {
303            drop(value);
304        }
305    }
306}
307
308impl<T: Clone> Clone for Array<T> {
309    /// Clones elements into a new array without sharing owned storage.
310    ///
311    /// - Complexity: O(n) time and space, excluding element clone costs.
312    ///
313    /// # Examples
314    ///
315    /// ```
316    /// use cel_runtime::Array;
317    /// let array = Array::from_vec(vec![String::from("leaf")]);
318    /// assert_eq!(array.clone().as_slice(), array.as_slice());
319    /// ```
320    fn clone(&self) -> Self {
321        Self::from_vec(self.as_slice().to_vec())
322    }
323}
324
325impl<T: PartialEq> PartialEq for Array<T> {
326    /// Compares lengths and corresponding elements using element equality.
327    ///
328    /// - Complexity: O(n) time in the worst case, excluding element comparison
329    ///   costs, and O(1) auxiliary space.
330    ///
331    /// # Examples
332    ///
333    /// ```
334    /// use cel_runtime::Array;
335    /// assert_eq!(Array::from_vec(vec![1_i32]), Array::from_vec(vec![1_i32]));
336    /// ```
337    fn eq(&self, other: &Self) -> bool {
338        self.as_slice() == other.as_slice()
339    }
340}
341
342impl<T: fmt::Debug> fmt::Debug for Array<T> {
343    /// Formats the array as `Array([elements])` using element debug formatting.
344    ///
345    /// - Complexity: O(n) element formatting calls, excluding element formatting
346    ///   costs and output storage.
347    ///
348    /// # Errors
349    ///
350    /// Returns an error if the formatter fails to write the representation.
351    ///
352    /// # Examples
353    ///
354    /// ```
355    /// use cel_runtime::Array;
356    /// assert_eq!(format!("{:?}", Array::from_vec(vec![1_i32])), "Array([1])");
357    /// ```
358    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
359        formatter
360            .debug_tuple("Array")
361            .field(&self.as_slice())
362            .finish()
363    }
364}
365
366#[cfg(test)]
367mod ownership_tests {
368    use super::*;
369    use std::cell::Cell;
370    use std::rc::Rc;
371
372    /// Releases members when only the erased owning header remains.
373    #[test]
374    fn raw_header_owns_its_members() {
375        /// Records the destruction of one member.
376        struct Tracked(Rc<Cell<usize>>);
377
378        impl Drop for Tracked {
379            /// Records release of this member.
380            fn drop(&mut self) {
381                self.0.set(self.0.get() + 1);
382            }
383        }
384
385        let drops = Rc::new(Cell::new(0));
386        let array = ManuallyDrop::new(Array::from_vec(vec![
387            Tracked(drops.clone()),
388            Tracked(drops.clone()),
389        ]));
390        // SAFETY: The suppressed typed owner transfers its header exactly once.
391        let raw = unsafe { std::ptr::read(&array.raw) };
392        drop(raw);
393        assert_eq!(drops.get(), 2);
394    }
395}