From 4680977573e1a5d10f59c74bf9e5bd9906eb0a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20J=C4=99drzejewski?= Date: Mon, 23 Mar 2026 11:24:02 +0100 Subject: [PATCH] alloc: allow `containers` to use custom allocators Reworked abstraction to allow usage of custom allocator implementations. --- Cargo.lock | 3 + src/containers/BUILD | 1 + src/containers/Cargo.toml | 3 + src/containers/fixed_capacity/mod.rs | 6 +- src/containers/fixed_capacity/queue.rs | 63 ++++++++--- src/containers/fixed_capacity/string.rs | 100 ++++++++++++++---- src/containers/fixed_capacity/vec.rs | 89 ++++++++++++---- src/containers/generic/queue.rs | 29 ++--- src/containers/generic/string.rs | 33 ++---- src/containers/generic/vec.rs | 30 ++---- src/containers/inline/queue.rs | 6 +- src/containers/inline/string.rs | 7 +- src/containers/inline/vec.rs | 7 +- src/containers/storage/heap.rs | 43 ++++---- src/containers/storage/inline.rs | 36 ++----- src/containers/storage/mod.rs | 42 +++----- src/elementary/allocator_traits.rs | 5 +- ...{global_allocator.rs => heap_allocator.rs} | 31 +++--- src/elementary/lib.rs | 7 +- src/sync/arc_in.rs | 22 ++-- 20 files changed, 334 insertions(+), 229 deletions(-) rename src/elementary/{global_allocator.rs => heap_allocator.rs} (64%) diff --git a/Cargo.lock b/Cargo.lock index f7ce2640..3500492d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,9 @@ version = 4 [[package]] name = "containers" version = "0.1.0" +dependencies = [ + "elementary", +] [[package]] name = "elementary" diff --git a/src/containers/BUILD b/src/containers/BUILD index 40d30abe..af9cb47e 100644 --- a/src/containers/BUILD +++ b/src/containers/BUILD @@ -18,6 +18,7 @@ rust_library( srcs = glob(["**/*.rs"]), edition = "2021", visibility = ["//visibility:public"], + deps = ["//src/elementary"], ) rust_test( diff --git a/src/containers/Cargo.toml b/src/containers/Cargo.toml index d57d1192..18a071be 100644 --- a/src/containers/Cargo.toml +++ b/src/containers/Cargo.toml @@ -21,3 +21,6 @@ license-file = "../../LICENSE.md" [lib] path = "lib.rs" + +[dependencies] +elementary.workspace = true diff --git a/src/containers/fixed_capacity/mod.rs b/src/containers/fixed_capacity/mod.rs index b6c5e18f..d2690a8b 100644 --- a/src/containers/fixed_capacity/mod.rs +++ b/src/containers/fixed_capacity/mod.rs @@ -15,6 +15,6 @@ mod queue; mod string; mod vec; -pub use self::queue::FixedCapacityQueue; -pub use self::string::FixedCapacityString; -pub use self::vec::FixedCapacityVec; +pub use self::queue::{FixedCapacityQueue, FixedCapacityQueueIn}; +pub use self::string::{FixedCapacityString, FixedCapacityStringIn}; +pub use self::vec::{FixedCapacityVec, FixedCapacityVecIn}; diff --git a/src/containers/fixed_capacity/queue.rs b/src/containers/fixed_capacity/queue.rs index 3ac6a10a..6cd3d594 100644 --- a/src/containers/fixed_capacity/queue.rs +++ b/src/containers/fixed_capacity/queue.rs @@ -11,20 +11,20 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -use core::ops; - use crate::generic::queue::GenericQueue; use crate::storage::Heap; +use core::ops; +use elementary::{BasicAllocator, HeapAllocator, GLOBAL_ALLOCATOR}; -/// A fixed-capacity queue. +/// A fixed-capacity queue, using provided allocator. /// /// The queue can hold between 0 and `CAPACITY` elements, and behaves similarly to Rust's `VecDeque`, /// except that it allocates memory immediately on construction, and can't shrink or grow. -pub struct FixedCapacityQueue { - inner: GenericQueue>, +pub struct FixedCapacityQueueIn<'alloc, T, A: BasicAllocator> { + inner: GenericQueue>, } -impl FixedCapacityQueue { +impl<'alloc, T, A: BasicAllocator> FixedCapacityQueueIn<'alloc, T, A> { /// Creates an empty queue and allocates memory for up to `capacity` elements, where `capacity <= u32::MAX`. /// /// # Panics @@ -32,42 +32,73 @@ impl FixedCapacityQueue { /// - Panics if `capacity > u32::MAX`. /// - Panics if the memory allocation fails. #[must_use] - pub fn new(capacity: usize) -> Self { + pub fn new(capacity: usize, alloc: &'alloc A) -> Self { assert!( capacity <= u32::MAX as usize, "FixedQueue can hold at most u32::MAX elements" ); - Self { - inner: GenericQueue::new(capacity as u32), - } + + let storage = Heap::new(capacity as u32, alloc); + let inner = GenericQueue::new(storage); + Self { inner } } } -impl Drop for FixedCapacityQueue { +impl Drop for FixedCapacityQueueIn<'_, T, A> { fn drop(&mut self) { self.inner.clear(); } } -impl ops::Deref for FixedCapacityQueue { - type Target = GenericQueue>; +impl<'alloc, T, A: BasicAllocator> ops::Deref for FixedCapacityQueueIn<'alloc, T, A> { + type Target = GenericQueue>; fn deref(&self) -> &Self::Target { &self.inner } } -impl ops::DerefMut for FixedCapacityQueue { +impl ops::DerefMut for FixedCapacityQueueIn<'_, T, A> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } +/// A fixed-capacity queue, using global allocator. +/// Refer to [`FixedCapacityQueueIn`] for more information. +pub struct FixedCapacityQueue(FixedCapacityQueueIn<'static, T, HeapAllocator>); + +impl FixedCapacityQueue { + /// Creates an empty queue and allocates memory for up to `capacity` elements, where `capacity <= u32::MAX`. + /// + /// # Panics + /// + /// - Panics if `capacity > u32::MAX`. + /// - Panics if the memory allocation fails. + #[must_use] + pub fn new(capacity: usize) -> Self { + Self(FixedCapacityQueueIn::new(capacity, &GLOBAL_ALLOCATOR)) + } +} + +impl ops::Deref for FixedCapacityQueue { + type Target = GenericQueue>; + + fn deref(&self) -> &Self::Target { + &self.0.inner + } +} + +impl ops::DerefMut for FixedCapacityQueue { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0.inner + } +} + #[cfg(test)] mod tests { - use std::collections::VecDeque; - use super::*; + use std::collections::VecDeque; fn to_vec((first, second): (&[T], &[T])) -> Vec { let mut elements = first.to_vec(); diff --git a/src/containers/fixed_capacity/string.rs b/src/containers/fixed_capacity/string.rs index aad77f16..7fba5943 100644 --- a/src/containers/fixed_capacity/string.rs +++ b/src/containers/fixed_capacity/string.rs @@ -11,23 +11,24 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -use core::fmt; -use core::ops; - use crate::generic::string::GenericString; use crate::storage::Heap; +use core::fmt; +use core::ops; +use elementary::GLOBAL_ALLOCATOR; +use elementary::{BasicAllocator, HeapAllocator}; -/// A fixed-capacity Unicode string. +/// A fixed-capacity Unicode string, using provided allocator.. /// /// Note that the string is encoded as UTF-8, so each character (Unicode codepoint) requires between 1 and 4 bytes of storage. /// /// The string can hold between 0 and `CAPACITY` **bytes**, and behaves similarly to Rust's `String`, /// except that it allocates memory immediately on construction, and can't shrink or grow. -pub struct FixedCapacityString { - inner: GenericString>, +pub struct FixedCapacityStringIn<'alloc, A: BasicAllocator> { + inner: GenericString>, } -impl FixedCapacityString { +impl<'alloc, A: BasicAllocator> FixedCapacityStringIn<'alloc, A> { /// Creates an empty string and allocates memory for up to `capacity` bytes, where `capacity <= u32::MAX`. /// /// Note that the string is encoded as UTF-8, so each character (Unicode codepoint) requires between 1 and 4 bytes of storage. @@ -37,14 +38,15 @@ impl FixedCapacityString { /// - Panics if `capacity > u32::MAX`. /// - Panics if the memory allocation fails. #[must_use] - pub fn new(capacity: usize) -> Self { + pub fn new(capacity: usize, alloc: &'alloc A) -> Self { assert!( capacity <= u32::MAX as usize, "FixedCapacityString can hold at most u32::MAX bytes" ); - Self { - inner: GenericString::new(capacity as u32), - } + + let storage = Heap::new(capacity as u32, alloc); + let inner = GenericString::new(storage); + Self { inner } } /// Tries to create an empty string for up to `capacity` bytes, where `capacity <= u32::MAX`. @@ -53,49 +55,105 @@ impl FixedCapacityString { /// /// Returns `None` if `capacity > u32::MAX`, or if the memory allocation fails. #[must_use] - pub fn try_new(capacity: usize) -> Option { + pub fn try_new(capacity: usize, alloc: &'alloc A) -> Option { if capacity <= u32::MAX as usize { - Some(Self { - inner: GenericString::try_new(capacity as u32)?, - }) + let storage = Heap::try_new(capacity as u32, alloc)?; + let inner = GenericString::new(storage); + Some(Self { inner }) } else { None } } } -impl Drop for FixedCapacityString { +impl Drop for FixedCapacityStringIn<'_, A> { fn drop(&mut self) { self.inner.clear(); } } -impl ops::Deref for FixedCapacityString { - type Target = GenericString>; +impl<'alloc, A: BasicAllocator> ops::Deref for FixedCapacityStringIn<'alloc, A> { + type Target = GenericString>; fn deref(&self) -> &Self::Target { &self.inner } } -impl ops::DerefMut for FixedCapacityString { +impl ops::DerefMut for FixedCapacityStringIn<'_, A> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } -impl fmt::Display for FixedCapacityString { +impl fmt::Display for FixedCapacityStringIn<'_, A> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } -impl fmt::Debug for FixedCapacityString { +impl fmt::Debug for FixedCapacityStringIn<'_, A> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.as_str(), f) } } +/// A fixed-capacity Unicode string, using global allocator. +/// Refer to [`FixedCapacityStringIn`] for more information. +pub struct FixedCapacityString(FixedCapacityStringIn<'static, HeapAllocator>); + +impl FixedCapacityString { + /// Creates an empty string and allocates memory for up to `capacity` bytes, where `capacity <= u32::MAX`. + /// + /// Note that the string is encoded as UTF-8, so each character (Unicode codepoint) requires between 1 and 4 bytes of storage. + /// + /// # Panics + /// + /// - Panics if `capacity > u32::MAX`. + /// - Panics if the memory allocation fails. + #[must_use] + pub fn new(capacity: usize) -> Self { + Self(FixedCapacityStringIn::new(capacity, &GLOBAL_ALLOCATOR)) + } + + /// Tries to create an empty string for up to `capacity` bytes, where `capacity <= u32::MAX`. + /// + /// Note that the string is encoded as UTF-8, so each character (Unicode codepoint) requires between 1 and 4 bytes of storage. + /// + /// Returns `None` if `capacity > u32::MAX`, or if the memory allocation fails. + #[must_use] + pub fn try_new(capacity: usize) -> Option { + let inner = FixedCapacityStringIn::try_new(capacity, &GLOBAL_ALLOCATOR)?; + Some(Self(inner)) + } +} + +impl ops::Deref for FixedCapacityString { + type Target = GenericString>; + + fn deref(&self) -> &Self::Target { + &self.0.inner + } +} + +impl ops::DerefMut for FixedCapacityString { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0.inner + } +} + +impl fmt::Display for FixedCapacityString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.0.as_str(), f) + } +} + +impl fmt::Debug for FixedCapacityString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self.0.as_str(), f) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/containers/fixed_capacity/vec.rs b/src/containers/fixed_capacity/vec.rs index c1bb928d..5679c164 100644 --- a/src/containers/fixed_capacity/vec.rs +++ b/src/containers/fixed_capacity/vec.rs @@ -11,21 +11,21 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -use core::fmt; -use core::ops; - use crate::generic::vec::GenericVec; use crate::storage::Heap; +use core::fmt; +use core::ops; +use elementary::{BasicAllocator, HeapAllocator, GLOBAL_ALLOCATOR}; -/// A fixed-capacity vector. +/// A fixed-capacity vector, using provided allocator. /// -/// The vector can hold between 0 and `CAPACITY` elements, and behaves similarly to Rust's `Vec`, +/// The vector can hold between 0 and `capacity` elements, and behaves similarly to Rust's `Vec`, /// except that it allocates memory immediately on construction, and can't shrink or grow. -pub struct FixedCapacityVec { - inner: GenericVec>, +pub struct FixedCapacityVecIn<'alloc, T, A: BasicAllocator> { + inner: GenericVec>, } -impl FixedCapacityVec { +impl<'alloc, T, A: BasicAllocator> FixedCapacityVecIn<'alloc, T, A> { /// Creates an empty vector and allocates memory for up to `capacity` elements, where `capacity <= u32::MAX`. /// /// # Panics @@ -33,57 +33,104 @@ impl FixedCapacityVec { /// - Panics if `capacity > u32::MAX`. /// - Panics if the memory allocation fails. #[must_use] - pub fn new(capacity: usize) -> Self { + pub fn new(capacity: usize, alloc: &'alloc A) -> Self { assert!( capacity <= u32::MAX as usize, "FixedCapacityVec can hold at most u32::MAX elements" ); - Self { - inner: GenericVec::new(capacity as u32), - } + + let storage = Heap::new(capacity as u32, alloc); + let inner = GenericVec::new(storage); + Self { inner } } /// Tries to create an empty vector for up to `capacity` elements, where `capacity <= u32::MAX`. /// /// Returns `None` if `capacity > u32::MAX`, or if the memory allocation fails. #[must_use] - pub fn try_new(capacity: usize) -> Option { + pub fn try_new(capacity: usize, alloc: &'alloc A) -> Option { if capacity <= u32::MAX as usize { - Some(Self { - inner: GenericVec::try_new(capacity as u32)?, - }) + let storage = Heap::try_new(capacity as u32, alloc)?; + let inner = GenericVec::new(storage); + Some(Self { inner }) } else { None } } } -impl Drop for FixedCapacityVec { +impl Drop for FixedCapacityVecIn<'_, T, A> { fn drop(&mut self) { self.inner.clear(); } } -impl ops::Deref for FixedCapacityVec { - type Target = GenericVec>; +impl<'alloc, T, A: BasicAllocator> ops::Deref for FixedCapacityVecIn<'alloc, T, A> { + type Target = GenericVec>; fn deref(&self) -> &Self::Target { &self.inner } } -impl ops::DerefMut for FixedCapacityVec { +impl ops::DerefMut for FixedCapacityVecIn<'_, T, A> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } -impl fmt::Debug for FixedCapacityVec { +impl fmt::Debug for FixedCapacityVecIn<'_, T, A> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.as_slice(), f) } } +/// A fixed-capacity vector, using global allocator. +/// Refer to [`FixedCapacityVecIn`] for more information. +pub struct FixedCapacityVec(FixedCapacityVecIn<'static, T, HeapAllocator>); + +impl FixedCapacityVec { + /// Creates an empty vector and allocates memory for up to `capacity` elements, where `capacity <= u32::MAX`. + /// + /// # Panics + /// + /// - Panics if `capacity > u32::MAX`. + /// - Panics if the memory allocation fails. + #[must_use] + pub fn new(capacity: usize) -> Self { + Self(FixedCapacityVecIn::new(capacity, &GLOBAL_ALLOCATOR)) + } + + /// Tries to create an empty vector for up to `capacity` elements, where `capacity <= u32::MAX`. + /// + /// Returns `None` if `capacity > u32::MAX`, or if the memory allocation fails. + #[must_use] + pub fn try_new(capacity: usize) -> Option { + let inner = FixedCapacityVecIn::try_new(capacity, &GLOBAL_ALLOCATOR)?; + Some(Self(inner)) + } +} + +impl ops::Deref for FixedCapacityVec { + type Target = GenericVec>; + + fn deref(&self) -> &Self::Target { + &self.0.inner + } +} + +impl ops::DerefMut for FixedCapacityVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0.inner + } +} + +impl fmt::Debug for FixedCapacityVec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self.0.as_slice(), f) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/containers/generic/queue.rs b/src/containers/generic/queue.rs index 296b5055..9cfdccae 100644 --- a/src/containers/generic/queue.rs +++ b/src/containers/generic/queue.rs @@ -33,12 +33,12 @@ pub struct GenericQueue> { } impl> GenericQueue { - /// Creates an empty queue. - pub fn new(capacity: u32) -> Self { + /// Creates an empty queue backed by the given storage. + pub fn new(storage: S) -> Self { Self { len: 0, front_index: 0, - storage: S::new(capacity), + storage, _marker: PhantomData, } } @@ -382,9 +382,9 @@ impl FusedIterator for IterMut<'_, T> {} #[cfg(test)] mod tests { - use std::{collections::VecDeque, mem::MaybeUninit}; - use super::*; + use crate::storage::test_utils::TestVec; + use std::collections::VecDeque; fn to_vec((first, second): (&[T], &[T])) -> Vec { let mut elements = first.to_vec(); @@ -394,7 +394,7 @@ mod tests { #[test] fn front_and_back() { - fn check_front_and_back(queue: &mut GenericQueue>>, control: &mut VecDeque) { + fn check_front_and_back(queue: &mut GenericQueue>, control: &mut VecDeque) { assert_eq!(queue.front(), control.front()); assert_eq!(queue.front_mut(), control.front_mut()); assert_eq!(queue.back(), control.back()); @@ -402,7 +402,8 @@ mod tests { } fn run_test(n: usize) { - let mut queue = GenericQueue::>>::new(n as u32); + let storage = TestVec::new(n); + let mut queue = GenericQueue::new(storage); let mut control = VecDeque::new(); // Completely fill and empty the queue n times, but move the internal start point @@ -437,7 +438,7 @@ mod tests { #[test] fn iter() { - fn check_iter(queue: &mut GenericQueue>>, control: &mut VecDeque) { + fn check_iter(queue: &mut GenericQueue>, control: &mut VecDeque) { // Test the Iterator::next() implementation: assert_eq!(queue.iter().collect::>(), control.iter().collect::>()); assert_eq!( @@ -456,7 +457,8 @@ mod tests { } fn run_test(n: usize) { - let mut queue = GenericQueue::>>::new(n as u32); + let storage = TestVec::new(n); + let mut queue = GenericQueue::new(storage); let mut control = VecDeque::new(); // Completely fill and empty the queue n times, but move the internal start point @@ -492,7 +494,8 @@ mod tests { #[test] fn push_back_and_pop_front() { fn run_test(n: usize) { - let mut queue = GenericQueue::>>::new(n as u32); + let storage = TestVec::new(n); + let mut queue = GenericQueue::new(storage); let mut control = VecDeque::new(); // Completely fill and empty the queue n times, but move the internal start point @@ -535,7 +538,8 @@ mod tests { #[test] fn push_front_and_pop_back() { fn run_test(n: usize) { - let mut queue = GenericQueue::>>::new(n as u32); + let storage = TestVec::new(n); + let mut queue = GenericQueue::new(storage); let mut control = VecDeque::new(); // Completely fill and empty the queue n times, but move the internal start point @@ -578,7 +582,8 @@ mod tests { #[test] fn is_empty_and_is_full() { fn run_test(n: usize) { - let mut queue = GenericQueue::>>::new(n as u32); + let storage = TestVec::new(n); + let mut queue = GenericQueue::new(storage); // Completely fill and empty the queue n times, but move the internal start point // ahead by one each time diff --git a/src/containers/generic/string.rs b/src/containers/generic/string.rs index e56229ba..a567d518 100644 --- a/src/containers/generic/string.rs +++ b/src/containers/generic/string.rs @@ -27,30 +27,15 @@ pub struct GenericString> { } impl> GenericString { - /// Creates an empty string with the given capacity in bytes. + /// Creates an empty string backed by the given storage. /// /// Note that the string is encoded as UTF-8, so each character (Unicode codepoint) requires between 1 and 4 bytes of storage. - /// - /// # Panics - /// - /// Panics if not enough memory could be allocated. - pub fn new(capacity: u32) -> Self { + pub fn new(storage: S) -> Self { Self { - vec: GenericVec::new(capacity), + vec: GenericVec::new(storage), } } - /// Tries to create an empty string with the given capacity in bytes. - /// - /// Note that the string is encoded as UTF-8, so each character (Unicode codepoint) requires between 1 and 4 bytes of storage. - /// - /// Returns `None` if not enough memory could be allocated. - pub fn try_new(capacity: u32) -> Option { - Some(Self { - vec: GenericVec::try_new(capacity)?, - }) - } - pub fn as_bytes(&self) -> &[u8] { self.vec.as_slice() } @@ -154,14 +139,14 @@ impl> fmt::Debug for GenericString { #[cfg(test)] mod tests { - use std::mem::MaybeUninit; - use super::*; + use crate::storage::test_utils::TestVec; #[test] fn push_and_pop() { fn run_test(n: usize) { - let mut string = GenericString::>>::new(n as u32); + let storage = TestVec::new(n); + let mut string = GenericString::new(storage); let mut control = String::new(); let result = string.pop(); @@ -196,7 +181,8 @@ mod tests { #[test] fn push_str() { fn run_test(n: usize) { - let mut string = GenericString::>>::new(n as u32); + let storage = TestVec::new(n); + let mut string = GenericString::new(storage); let mut control = String::new(); let samples = ["abc", "\0", "πŸ˜‰", "πŸ‘πŸΌπŸš€", "Ξ±Ξ²Ξ³"]; @@ -224,7 +210,8 @@ mod tests { #[test] fn is_full_and_is_empty() { fn run_test(n: usize) { - let mut string = GenericString::>>::new(n as u32); + let storage = TestVec::new(n); + let mut string = GenericString::new(storage); assert!(string.is_empty()); let sample = "abcdefghi"; diff --git a/src/containers/generic/vec.rs b/src/containers/generic/vec.rs index 3cff0d34..13345bab 100644 --- a/src/containers/generic/vec.rs +++ b/src/containers/generic/vec.rs @@ -28,30 +28,15 @@ pub struct GenericVec> { } impl> GenericVec { - /// Creates an empty vector with the given capacity. - /// - /// # Panics - /// - /// Panics if not enough memory could be allocated. - pub fn new(capacity: u32) -> Self { + /// Creates an empty vector backed by the given storage. + pub fn new(storage: S) -> Self { Self { len: 0, - storage: S::new(capacity), + storage, _marker: PhantomData, } } - /// Tries to create an empty vector with the given capacity. - /// - /// Returns `None` if not enough memory could be allocated. - pub fn try_new(capacity: u32) -> Option { - Some(Self { - len: 0, - storage: S::try_new(capacity)?, - _marker: PhantomData, - }) - } - /// Extracts a slice containing the entire vector. /// /// Equivalent to `&v[..]`. @@ -192,14 +177,14 @@ impl> fmt::Debug for GenericVec { #[cfg(test)] mod tests { - use std::mem::MaybeUninit; - use super::*; + use crate::storage::test_utils::TestVec; #[test] fn push_and_pop() { fn run_test(n: usize) { - let mut vector = GenericVec::>>::new(n as u32); + let storage = TestVec::new(n); + let mut vector = GenericVec::new(storage); let mut control = vec![]; let result = vector.pop(); @@ -234,7 +219,8 @@ mod tests { #[test] fn is_full_and_is_empty() { fn run_test(n: usize) { - let mut vector = GenericVec::>>::new(n as u32); + let storage = TestVec::new(n); + let mut vector = GenericVec::new(storage); assert!(vector.is_empty()); for i in 0..n { diff --git a/src/containers/inline/queue.rs b/src/containers/inline/queue.rs index 61167a6e..2c502948 100644 --- a/src/containers/inline/queue.rs +++ b/src/containers/inline/queue.rs @@ -47,9 +47,9 @@ impl InlineQueue { pub fn new() -> Self { let () = Self::CHECK_CAPACITY; - Self { - inner: GenericQueue::new(CAPACITY as u32), - } + let storage = Inline::::new(); + let inner = GenericQueue::new(storage); + Self { inner } } } diff --git a/src/containers/inline/string.rs b/src/containers/inline/string.rs index 5b12b31f..f1e3b40f 100644 --- a/src/containers/inline/string.rs +++ b/src/containers/inline/string.rs @@ -45,12 +45,13 @@ impl InlineString { const CHECK_CAPACITY: () = assert!(0 < CAPACITY && CAPACITY <= u32::MAX as usize); /// Creates an empty string. + #[must_use] pub fn new() -> Self { let () = Self::CHECK_CAPACITY; - Self { - inner: GenericString::new(CAPACITY as u32), - } + let storage = Inline::<_, CAPACITY>::new(); + let inner = GenericString::new(storage); + Self { inner } } } diff --git a/src/containers/inline/vec.rs b/src/containers/inline/vec.rs index ab71131a..2dcefff5 100644 --- a/src/containers/inline/vec.rs +++ b/src/containers/inline/vec.rs @@ -43,12 +43,13 @@ impl InlineVec { const CHECK_CAPACITY: () = assert!(0 < CAPACITY && CAPACITY <= u32::MAX as usize); /// Creates an empty vector. + #[must_use] pub fn new() -> Self { let () = Self::CHECK_CAPACITY; - Self { - inner: GenericVec::new(CAPACITY as u32), - } + let storage = Inline::::new(); + let inner = GenericVec::new(storage); + Self { inner } } } diff --git a/src/containers/storage/heap.rs b/src/containers/storage/heap.rs index b7520561..ccef0991 100644 --- a/src/containers/storage/heap.rs +++ b/src/containers/storage/heap.rs @@ -11,31 +11,31 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -use alloc::alloc::alloc; -use alloc::alloc::dealloc; +use crate::storage::Storage; use alloc::alloc::Layout; use core::marker::PhantomData; use core::mem::MaybeUninit; use core::ptr; use core::ptr::NonNull; - -use super::Storage; +use elementary::BasicAllocator; /// Fixed-capacity, heap-allocated storage. -pub struct Heap { +pub struct Heap<'alloc, T, A: BasicAllocator> { /// Allocated capacity, in number of elements. capacity: u32, /// Pointer to the allocated memory. /// /// If `self.capacity > 0`, this points to an allocated memory area of size `self.capacity * size_of` and alignment `align_of`. elements: NonNull, + /// Allocator used by the storage. + alloc: &'alloc A, _marker: PhantomData, } -// SAFETY: `Heap` can be sent to another thread if `T` can be sent to another thread. It's because we use system allocation which is send-safe. -unsafe impl Send for Heap {} +// SAFETY: `Heap` can be sent to another thread if `T` can be sent to another thread and used allocator is send-safe. +unsafe impl Send for Heap<'_, T, A> {} -impl Heap { +impl Heap<'_, T, A> { fn layout(capacity: u32) -> Option { (capacity as usize) .checked_mul(size_of::()) @@ -43,14 +43,14 @@ impl Heap { } } -impl Storage for Heap { +impl<'alloc, T, A: BasicAllocator> Heap<'alloc, T, A> { /// Creates a new instance with capacity for exactly the given number of elements. /// /// # Panics /// /// Panics if the memory allocation failed. - fn new(capacity: u32) -> Self { - Self::try_new(capacity).unwrap_or_else(|| { + pub fn new(capacity: u32, alloc: &'alloc A) -> Self { + Self::try_new(capacity, alloc).unwrap_or_else(|| { panic!( "failed to allocate {capacity} elements of {typ}", typ = core::any::type_name::() @@ -61,21 +61,24 @@ impl Storage for Heap { /// Tries to create a new instance with capacity for exactly the given number of elements. /// /// Returns `None` if the memory allocation failed. - fn try_new(capacity: u32) -> Option { + pub fn try_new(capacity: u32, alloc: &'alloc A) -> Option { let storage = if capacity > 0 { let layout = Self::layout(capacity)?; // SAFETY: `layout` has a non-zero size (because `capacity` is > 0) - NonNull::new(unsafe { alloc(layout) })? + NonNull::new(alloc.allocate(layout).ok()?.as_ptr())? } else { NonNull::dangling() }; Some(Self { capacity, elements: storage.cast::(), + alloc, _marker: PhantomData, }) } +} +impl Storage for Heap<'_, T, A> { fn capacity(&self) -> u32 { self.capacity } @@ -119,7 +122,7 @@ impl Storage for Heap { } } -impl Drop for Heap { +impl Drop for Heap<'_, T, A> { fn drop(&mut self) { if self.capacity > 0 { let layout = Self::layout(self.capacity).unwrap(); @@ -127,7 +130,8 @@ impl Drop for Heap { // - `self.elements` has previously been allocated with `alloc` // - `layout` is the same as the one used for the allocation unsafe { - dealloc(self.elements.as_ptr().cast::(), layout); + let ptr = self.elements.cast(); + self.alloc.deallocate(ptr, layout); } } } @@ -136,13 +140,14 @@ impl Drop for Heap { #[cfg(test)] mod tests { use super::*; + use elementary::{HeapAllocator, GLOBAL_ALLOCATOR}; #[test] fn subslice() { type T = u64; fn run_test(capacity: u32) { - let instance = Heap::::new(capacity); + let instance = Heap::::new(capacity, &GLOBAL_ALLOCATOR); let empty_slice = unsafe { instance.subslice(0, 0) }; assert_eq!(empty_slice.len(), 0); @@ -176,7 +181,7 @@ mod tests { type T = u64; fn run_test(capacity: u32) { - let mut instance = Heap::::new(capacity); + let mut instance = Heap::::new(capacity, &GLOBAL_ALLOCATOR); let empty_slice = unsafe { instance.subslice_mut(0, 0) }; assert_eq!(empty_slice.len(), 0); @@ -210,7 +215,7 @@ mod tests { type T = u64; fn run_test(capacity: u32) { - let instance = Heap::::new(capacity); + let instance = Heap::::new(capacity, &GLOBAL_ALLOCATOR); if capacity >= 1 { let first_element = unsafe { instance.element(0) }; @@ -245,7 +250,7 @@ mod tests { type T = u64; fn run_test(capacity: u32) { - let mut instance = Heap::::new(capacity); + let mut instance = Heap::::new(capacity, &GLOBAL_ALLOCATOR); if capacity >= 1 { let first_element = unsafe { instance.element_mut(0) }; diff --git a/src/containers/storage/inline.rs b/src/containers/storage/inline.rs index 05bdff3f..20dc1d04 100644 --- a/src/containers/storage/inline.rs +++ b/src/containers/storage/inline.rs @@ -28,38 +28,24 @@ impl Inline { // Compile-time check. This condition _must_ be referenced in every function that depends on it, // otherwise it will be removed during monomorphization. const CHECK_CAPACITY: () = assert!(0 < CAPACITY && CAPACITY <= (u32::MAX as usize)); -} -impl Storage for Inline { /// Creates a new instance. - /// - /// # Panics - /// - /// Panics if and only if `capacity != CAPACITY`. - fn new(capacity: u32) -> Self { + pub fn new() -> Self { let () = Self::CHECK_CAPACITY; - assert_eq!(capacity as usize, CAPACITY); Self { elements: [const { MaybeUninit::uninit() }; CAPACITY], } } +} - /// Tries to create a new instance. - /// - /// Returns `None` if and only if `capacity != CAPACITY`. - fn try_new(capacity: u32) -> Option { - let () = Self::CHECK_CAPACITY; - - if capacity as usize == CAPACITY { - Some(Self { - elements: [const { MaybeUninit::uninit() }; CAPACITY], - }) - } else { - None - } +impl Default for Inline { + fn default() -> Self { + Self::new() } +} +impl Storage for Inline { fn capacity(&self) -> u32 { let () = Self::CHECK_CAPACITY; @@ -119,7 +105,7 @@ mod tests { fn run_test() { let capacity = N as u32; - let instance = Inline::::new(capacity); + let instance = Inline::::new(); let empty_slice = unsafe { instance.subslice(0, 0) }; assert_eq!(empty_slice.len(), 0); @@ -159,7 +145,7 @@ mod tests { fn run_test() { let capacity = N as u32; - let mut instance = Inline::::new(capacity); + let mut instance = Inline::::new(); let empty_slice = unsafe { instance.subslice_mut(0, 0) }; assert_eq!(empty_slice.len(), 0); @@ -199,7 +185,7 @@ mod tests { fn run_test() { let capacity = N as u32; - let instance = Inline::::new(capacity); + let instance = Inline::::new(); if capacity >= 1 { let first_element = unsafe { instance.element(0) }; @@ -240,7 +226,7 @@ mod tests { fn run_test() { let capacity = N as u32; - let mut instance = Inline::::new(capacity); + let mut instance = Inline::::new(); if capacity >= 1 { let first_element = unsafe { instance.element_mut(0) }; diff --git a/src/containers/storage/mod.rs b/src/containers/storage/mod.rs index ea60cc7d..4e56b484 100644 --- a/src/containers/storage/mod.rs +++ b/src/containers/storage/mod.rs @@ -23,23 +23,9 @@ use core::mem::MaybeUninit; /// /// # Panics /// -/// With the exception of [`new`](Storage::new), the methods in this trait are *not* allowed to panic when `cfg(debug_assertions)` is not enabled. +/// The methods in this trait are *not* allowed to panic when `cfg(debug_assertions)` is not enabled. /// Implementors should use `debug_assert!` to check that preconditions are fulfilled. pub trait Storage { - /// Creates a new instance with enough capacity for the given number of elements. - /// - /// # Panics - /// - /// This method is allowed to panic when `capacity` is invalid, or when not enough memory could be allocated. - fn new(capacity: u32) -> Self; - - /// Tries to create a new instance with enough capacity for the given number of elements. - /// - /// Returns `None` if the allocation failed for any reason. - fn try_new(capacity: u32) -> Option - where - Self: Sized; - /// Returns the allocated capacity. fn capacity(&self) -> u32; @@ -73,44 +59,48 @@ pub trait Storage { } #[cfg(test)] -mod test_utils { +pub(crate) mod test_utils { //! A simple impl of [`Storage`] for [`Vec`], to be used for tests of generic containers. use super::*; use core::ptr; - impl Storage for Vec> { - fn new(capacity: u32) -> Self { + pub struct TestVec(Vec>); + + impl TestVec { + pub fn new(capacity: usize) -> Self { Self::try_new(capacity).unwrap_or_else(|| panic!("failed to allocate for {capacity} elements")) } - fn try_new(capacity: u32) -> Option + pub fn try_new(capacity: usize) -> Option where Self: Sized, { let mut instance = vec![]; - instance.try_reserve_exact(capacity as usize).ok()?; + instance.try_reserve_exact(capacity).ok()?; instance.extend((0..capacity).map(|_| MaybeUninit::zeroed())); - Some(instance) + Some(Self(instance)) } + } + impl Storage for TestVec { fn capacity(&self) -> u32 { - self.capacity() as u32 + self.0.capacity() as u32 } unsafe fn element(&self, index: u32) -> &MaybeUninit { - &self[index as usize] + &self.0[index as usize] } unsafe fn element_mut(&mut self, index: u32) -> &mut MaybeUninit { - &mut self[index as usize] + &mut self.0[index as usize] } unsafe fn subslice(&self, start: u32, end: u32) -> *const [T] { debug_assert!(start <= end); debug_assert!(end <= Storage::capacity(self)); // SAFETY: `start` is in-bounds of the array, as per the pre-condition on the trait method. - let ptr = unsafe { self.as_ptr().add(start as usize).cast() }; + let ptr = unsafe { self.0.as_ptr().add(start as usize).cast() }; let len = end - start; ptr::slice_from_raw_parts(ptr, len as usize) } @@ -119,7 +109,7 @@ mod test_utils { debug_assert!(start <= end); debug_assert!(end <= Storage::capacity(self)); // SAFETY: `start` is in-bounds of the array, as per the pre-condition on the trait method. - let ptr = unsafe { self.as_mut_ptr().add(start as usize).cast() }; + let ptr = unsafe { self.0.as_mut_ptr().add(start as usize).cast() }; let len = end - start; ptr::slice_from_raw_parts_mut(ptr, len as usize) } diff --git a/src/elementary/allocator_traits.rs b/src/elementary/allocator_traits.rs index ebf25026..7553737b 100644 --- a/src/elementary/allocator_traits.rs +++ b/src/elementary/allocator_traits.rs @@ -11,6 +11,7 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* +use core::alloc::Layout; use core::ptr::NonNull; #[derive(Debug, Clone, Copy)] @@ -27,7 +28,7 @@ pub enum AllocationError { pub trait BasicAllocator { /// Allocates a block of memory as described by `layout`. - fn allocate(&self, layout: core::alloc::Layout) -> Result, AllocationError>; + fn allocate(&self, layout: Layout) -> Result, AllocationError>; /// Deallocates the memory block pointed to by `ptr` with the given `layout`. /// @@ -35,5 +36,5 @@ pub trait BasicAllocator { /// - `ptr` must have been allocated by a previous call to `allocate` with the same `layout`. /// - `layout` must match the layout used during allocation. /// - unsafe fn deallocate(&self, ptr: NonNull, layout: core::alloc::Layout); + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout); } diff --git a/src/elementary/global_allocator.rs b/src/elementary/heap_allocator.rs similarity index 64% rename from src/elementary/global_allocator.rs rename to src/elementary/heap_allocator.rs index 08f58d31..5276791b 100644 --- a/src/elementary/global_allocator.rs +++ b/src/elementary/heap_allocator.rs @@ -12,18 +12,22 @@ // ******************************************************************************* extern crate alloc; -use alloc::alloc::alloc; -use alloc::alloc::dealloc; +use alloc::alloc::{alloc, dealloc}; +use core::alloc::Layout; use core::ptr::NonNull; use crate::allocator_traits::{AllocationError, BasicAllocator}; -#[derive(Debug, Clone, Copy)] -pub struct GlobalAllocator; +/// Global allocator. +pub static GLOBAL_ALLOCATOR: HeapAllocator = HeapAllocator; -impl BasicAllocator for GlobalAllocator { - fn allocate(&self, layout: core::alloc::Layout) -> Result, AllocationError> { +/// Proxy to global heap allocation in Rust. +#[derive(Debug, Default, Clone, Copy)] +pub struct HeapAllocator; + +impl BasicAllocator for HeapAllocator { + fn allocate(&self, layout: Layout) -> Result, AllocationError> { if layout.size() == 0 { return Err(AllocationError::ZeroSizeAllocation); } @@ -33,20 +37,13 @@ impl BasicAllocator for GlobalAllocator { if ptr.is_null() { return Err(AllocationError::OutOfMemory); } - Ok(NonNull::slice_from_raw_parts( - NonNull::new_unchecked(ptr), - layout.size(), - )) + + // SAFETY: already checked for null. + Ok(NonNull::new_unchecked(ptr)) } } - unsafe fn deallocate(&self, ptr: NonNull, layout: core::alloc::Layout) { + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { dealloc(ptr.as_ptr(), layout); } } - -impl Default for GlobalAllocator { - fn default() -> Self { - GlobalAllocator - } -} diff --git a/src/elementary/lib.rs b/src/elementary/lib.rs index 7962df7b..6eeaa4e9 100644 --- a/src/elementary/lib.rs +++ b/src/elementary/lib.rs @@ -11,5 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -pub mod allocator_traits; -pub mod global_allocator; +mod allocator_traits; +mod heap_allocator; + +pub use allocator_traits::{AllocationError, BasicAllocator}; +pub use heap_allocator::{HeapAllocator, GLOBAL_ALLOCATOR}; diff --git a/src/sync/arc_in.rs b/src/sync/arc_in.rs index bfc8b6d9..459f9e46 100644 --- a/src/sync/arc_in.rs +++ b/src/sync/arc_in.rs @@ -20,7 +20,7 @@ use std::{ sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}, }; -use elementary::allocator_traits::BasicAllocator; +use elementary::BasicAllocator; /// A reference-counted smart pointer with custom allocator support like `std::sync::Arc`. /// The `ArcIn` type provides shared ownership of a value of type `T`, allocated using the specified allocator `A`. @@ -155,13 +155,13 @@ impl Drop for ArcIn { #[cfg(test)] mod tests { use super::*; - use elementary::global_allocator::GlobalAllocator; + use elementary::HeapAllocator; use std::collections::hash_map::DefaultHasher; use std::hash::Hash; #[test] fn new_and_deref() { - let alloc = GlobalAllocator; + let alloc = HeapAllocator; let arc = ArcIn::new_in(42, alloc); assert_eq!(*arc, 42); assert_eq!(ArcIn::strong_count(&arc), 1); @@ -169,7 +169,7 @@ mod tests { #[test] fn clone_increases_count() { - let alloc = GlobalAllocator; + let alloc = HeapAllocator; let arc1 = ArcIn::new_in(100, alloc); let arc2 = arc1.clone(); assert_eq!(ArcIn::strong_count(&arc1), 2); @@ -178,7 +178,7 @@ mod tests { #[test] fn drop_decreases_count() { - let alloc = GlobalAllocator; + let alloc = HeapAllocator; let arc1 = ArcIn::new_in(55, alloc); assert_eq!(ArcIn::strong_count(&arc1), 1); { @@ -191,7 +191,7 @@ mod tests { #[test] fn debug_trait() { - let alloc = GlobalAllocator; + let alloc = HeapAllocator; let arc = ArcIn::new_in("hello", alloc); let s = format!("{:?}", arc); assert_eq!(s, "\"hello\""); @@ -199,14 +199,14 @@ mod tests { #[test] fn default_trait() { - let arc: ArcIn = ArcIn::default(); + let arc: ArcIn = ArcIn::default(); assert_eq!(*arc, 0); assert_eq!(ArcIn::strong_count(&arc), 1); } #[test] fn as_ref_trait() { - let alloc = GlobalAllocator; + let alloc = HeapAllocator; let arc = ArcIn::new_in("world".to_string(), alloc); let s: &str = arc.as_ref(); assert_eq!(s, "world"); @@ -214,7 +214,7 @@ mod tests { #[test] fn eq_ord_hash() { - let alloc = GlobalAllocator; + let alloc = HeapAllocator; let arc1 = ArcIn::new_in(5, alloc); let arc2 = ArcIn::new_in(5, alloc); let arc3 = ArcIn::new_in(10, alloc); @@ -233,7 +233,7 @@ mod tests { #[test] fn strong_count_multiple_clones() { - let alloc = GlobalAllocator; + let alloc = HeapAllocator; let arc = ArcIn::new_in(123, alloc); let clones: Vec<_> = (0..5).map(|_| arc.clone()).collect(); assert_eq!(ArcIn::strong_count(&arc), 6); @@ -250,7 +250,7 @@ mod tests { } } - let alloc = GlobalAllocator; + let alloc = HeapAllocator; let mut dropped = false; { let arc = ArcIn::new_in(DropCounter(&mut dropped), alloc);