Skip to main content

fbl/
array.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7use core::ops::{Deref, DerefMut};
8use kalloc::{AllocError, Allocator, Box, DefaultAllocator};
9
10/// A fixed-size array that takes ownership of its elements.
11/// This is a Rust analog to `fbl::Array` in C++.
12pub struct Array<T, A: Allocator = DefaultAllocator> {
13    buf: Box<[T], A>,
14}
15
16zr::static_assert!(core::mem::size_of::<Array<u32>>() == 16);
17zr::static_assert!(core::mem::align_of::<Array<u32>>() == 8);
18
19impl<T, A: Allocator> Array<T, A> {
20    /// Creates an empty array with the given allocator.
21    pub const fn new_in(allocator: A) -> Self {
22        Self { buf: Box::empty_slice_in(allocator) }
23    }
24
25    /// Creates an array from a Box.
26    pub fn from_box(buf: Box<[T], A>) -> Self {
27        Self { buf }
28    }
29
30    /// Allocates a new array of the given length, default-constructing each element.
31    pub fn try_new_in(len: usize, allocator: A) -> Result<Self, AllocError>
32    where
33        T: Default,
34    {
35        let mut b = Box::<[T], A>::try_new_uninit_slice_in(len, allocator)?;
36        for i in 0..len {
37            b[i].write(T::default());
38        }
39        // SAFETY: All elements have been initialized.
40        Ok(Self { buf: unsafe { b.assume_init() } })
41    }
42
43    /// Returns the number of elements in the array.
44    pub fn len(&self) -> usize {
45        self.buf.len()
46    }
47
48    /// Returns true if the array is empty.
49    pub fn is_empty(&self) -> bool {
50        self.buf.is_empty()
51    }
52
53    /// Consumes the array and returns the inner Box.
54    pub fn into_box(self) -> Box<[T], A> {
55        self.buf
56    }
57}
58
59impl<T> Array<T, DefaultAllocator> {
60    /// Creates an empty array using the default allocator.
61    pub const fn new() -> Self {
62        Self { buf: Box::empty_slice() }
63    }
64
65    /// Allocates a new array of the given length, default-constructing each element.
66    pub fn try_new(len: usize) -> Result<Self, AllocError>
67    where
68        T: Default,
69    {
70        Self::try_new_in(len, DefaultAllocator)
71    }
72}
73
74impl<T, A: Allocator> Deref for Array<T, A> {
75    type Target = [T];
76
77    fn deref(&self) -> &Self::Target {
78        &self.buf
79    }
80}
81
82impl<T, A: Allocator> DerefMut for Array<T, A> {
83    fn deref_mut(&mut self) -> &mut Self::Target {
84        &mut self.buf
85    }
86}
87
88impl<T> Default for Array<T, DefaultAllocator> {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use core::cell::Cell;
98    use core::ptr::NonNull;
99
100    #[derive(Debug, PartialEq, Eq)]
101    struct TestState {
102        live_obj_count: Cell<usize>,
103        ctor_count: Cell<usize>,
104        dtor_count: Cell<usize>,
105        alloc_count: Cell<usize>,
106        fail_threshold: Cell<usize>,
107    }
108
109    impl Default for TestState {
110        fn default() -> Self {
111            Self {
112                live_obj_count: Cell::new(0),
113                ctor_count: Cell::new(0),
114                dtor_count: Cell::new(0),
115                alloc_count: Cell::new(0),
116                fail_threshold: Cell::new(usize::MAX),
117            }
118        }
119    }
120
121    #[derive(Clone)]
122    struct TestAllocator<'a> {
123        state: &'a TestState,
124    }
125
126    impl<'a> kalloc::Allocator for TestAllocator<'a> {
127        fn allocate(&self, layout: core::alloc::Layout) -> Result<NonNull<[u8]>, AllocError> {
128            let current = self.state.alloc_count.get();
129            self.state.alloc_count.set(current + 1);
130            if current >= self.state.fail_threshold.get() {
131                return Err(AllocError);
132            }
133            DefaultAllocator::default().allocate(layout)
134        }
135
136        unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: core::alloc::Layout) {
137            unsafe { DefaultAllocator::default().deallocate(ptr, layout) }
138        }
139
140        unsafe fn grow(
141            &self,
142            ptr: NonNull<u8>,
143            old_layout: core::alloc::Layout,
144            new_layout: core::alloc::Layout,
145        ) -> Result<NonNull<[u8]>, AllocError> {
146            let current = self.state.alloc_count.get();
147            self.state.alloc_count.set(current + 1);
148            if current >= self.state.fail_threshold.get() {
149                return Err(AllocError);
150            }
151            unsafe { DefaultAllocator::default().grow(ptr, old_layout, new_layout) }
152        }
153
154        unsafe fn shrink(
155            &self,
156            ptr: NonNull<u8>,
157            old_layout: core::alloc::Layout,
158            new_layout: core::alloc::Layout,
159        ) -> Result<NonNull<[u8]>, AllocError> {
160            let current = self.state.alloc_count.get();
161            self.state.alloc_count.set(current + 1);
162            if current >= self.state.fail_threshold.get() {
163                return Err(AllocError);
164            }
165            unsafe { DefaultAllocator::default().shrink(ptr, old_layout, new_layout) }
166        }
167
168        fn allocate_zeroed(
169            &self,
170            layout: core::alloc::Layout,
171        ) -> Result<NonNull<[u8]>, AllocError> {
172            let current = self.state.alloc_count.get();
173            self.state.alloc_count.set(current + 1);
174            if current >= self.state.fail_threshold.get() {
175                return Err(AllocError);
176            }
177            DefaultAllocator::default().allocate_zeroed(layout)
178        }
179    }
180
181    #[derive(Debug, Eq, PartialEq)]
182    struct TestObject<'a> {
183        val: usize,
184        alive: bool,
185        state: &'a TestState,
186    }
187
188    impl<'a> TestObject<'a> {
189        fn new(val: usize, state: &'a TestState) -> Self {
190            state.live_obj_count.set(state.live_obj_count.get() + 1);
191            state.ctor_count.set(state.ctor_count.get() + 1);
192            TestObject { val, alive: true, state }
193        }
194    }
195
196    impl<'a> Drop for TestObject<'a> {
197        fn drop(&mut self) {
198            if self.alive {
199                self.state.live_obj_count.set(self.state.live_obj_count.get() - 1);
200                self.state.dtor_count.set(self.state.dtor_count.get() + 1);
201            }
202        }
203    }
204
205    #[test]
206    fn test_empty_array() {
207        let a: Array<u32> = Array::new();
208        assert_eq!(a.len(), 0);
209        assert!(a.is_empty());
210    }
211
212    #[test]
213    fn test_try_new() {
214        let a = Array::<u32>::try_new(5).unwrap();
215        assert_eq!(a.len(), 5);
216        for i in 0..5 {
217            assert_eq!(a[i], 0);
218        }
219    }
220
221    #[test]
222    fn test_deref() {
223        let mut a = Array::<u32>::try_new(2).unwrap();
224        a[0] = 10;
225        a[1] = 20;
226
227        let slice: &[u32] = &a;
228        assert_eq!(slice, &[10, 20]);
229
230        let slice_mut: &mut [u32] = &mut a;
231        slice_mut[0] = 30;
232        assert_eq!(a[0], 30);
233    }
234
235    #[test]
236    fn test_drop_behavior() {
237        let state = TestState::default();
238        {
239            let mut b = Box::<[TestObject<'_>], TestAllocator<'_>>::try_new_uninit_slice_in(
240                2,
241                TestAllocator { state: &state },
242            )
243            .unwrap();
244            b[0].write(TestObject::new(1, &state));
245            b[1].write(TestObject::new(2, &state));
246            let _a = Array::from_box(unsafe { b.assume_init() });
247            assert_eq!(state.live_obj_count.get(), 2);
248        }
249        assert_eq!(state.live_obj_count.get(), 0);
250        assert_eq!(state.dtor_count.get(), 2);
251    }
252
253    #[test]
254    fn test_allocation_failure() {
255        let state = TestState::default();
256        state.fail_threshold.set(0); // Fail immediately
257
258        let res = Array::<u32, TestAllocator<'_>>::try_new_in(5, TestAllocator { state: &state });
259        assert!(res.is_err());
260    }
261
262    #[test]
263    fn test_try_new_zero_sized() {
264        let state = TestState::default();
265        let a = Array::<u32, TestAllocator<'_>>::try_new_in(0, TestAllocator { state: &state })
266            .unwrap();
267        assert_eq!(a.len(), 0);
268        assert!(a.is_empty());
269    }
270
271    #[test]
272    fn test_non_trivial_default() {
273        #[derive(Debug, PartialEq, Eq)]
274        struct MyInt {
275            value: i32,
276        }
277        impl Default for MyInt {
278            fn default() -> Self {
279                Self { value: 42 }
280            }
281        }
282
283        let a = Array::<MyInt>::try_new(5).unwrap();
284        assert_eq!(a.len(), 5);
285        for i in 0..5 {
286            assert_eq!(a[i].value, 42);
287        }
288    }
289
290    #[test]
291    fn test_array_new_in() {
292        let state = TestState::default();
293        let alloc = TestAllocator { state: &state };
294        let a = Array::<u32, TestAllocator<'_>>::new_in(alloc.clone());
295        assert!(a.is_empty());
296    }
297
298    #[test]
299    fn test_array_default() {
300        let a_def: Array<u32> = Default::default();
301        assert!(a_def.is_empty());
302    }
303
304    #[test]
305    fn test_array_into_box() {
306        let a_try = Array::<u32>::try_new(3).unwrap();
307        let b = a_try.into_box();
308        assert_eq!(b.len(), 3);
309    }
310
311    #[test]
312    fn test_array_test_allocator_happy() {
313        use kalloc::Allocator;
314        let state = TestState::default();
315        let alloc = TestAllocator { state: &state };
316        let layout = core::alloc::Layout::new::<u32>();
317        let ptr = alloc.allocate_zeroed(layout).unwrap();
318
319        let ptr = unsafe {
320            alloc.grow(ptr.cast(), layout, core::alloc::Layout::array::<u32>(2).unwrap()).unwrap()
321        };
322
323        let ptr = unsafe {
324            alloc.shrink(ptr.cast(), core::alloc::Layout::array::<u32>(2).unwrap(), layout).unwrap()
325        };
326
327        unsafe {
328            alloc.deallocate(ptr.cast(), layout);
329        }
330    }
331
332    #[test]
333    fn test_array_test_allocator_failure() {
334        use kalloc::Allocator;
335        let state = TestState::default();
336        let alloc = TestAllocator { state: &state };
337        let layout = core::alloc::Layout::new::<u32>();
338
339        // Set fail threshold to fail immediately
340        state.fail_threshold.set(0);
341
342        assert!(alloc.allocate_zeroed(layout).is_err());
343
344        let dummy_ptr = core::ptr::NonNull::<u8>::dangling();
345        assert!(
346            unsafe {
347                alloc.grow(dummy_ptr.cast(), layout, core::alloc::Layout::array::<u32>(2).unwrap())
348            }
349            .is_err()
350        );
351        assert!(
352            unsafe {
353                alloc.shrink(
354                    dummy_ptr.cast(),
355                    core::alloc::Layout::array::<u32>(2).unwrap(),
356                    layout,
357                )
358            }
359            .is_err()
360        );
361    }
362}