From 7f7d8ff8f3e263a19ad537867c15c799237719fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Willenb=C3=BCcher?= Date: Tue, 2 Dec 2025 14:05:33 +0100 Subject: [PATCH] Implement inline-storage and fixed-capacity queues --- src/containers/fixed_capacity/mod.rs | 2 + src/containers/fixed_capacity/queue.rs | 197 ++++++++++++++ src/containers/generic/mod.rs | 1 + src/containers/generic/queue.rs | 352 +++++++++++++++++++++++++ src/containers/generic/vec.rs | 70 +++++ src/containers/inline/mod.rs | 2 + src/containers/inline/queue.rs | 216 +++++++++++++++ src/containers/inline/vec.rs | 13 +- src/containers/storage/mod.rs | 33 +++ 9 files changed, 885 insertions(+), 1 deletion(-) create mode 100644 src/containers/fixed_capacity/queue.rs create mode 100644 src/containers/generic/queue.rs create mode 100644 src/containers/inline/queue.rs diff --git a/src/containers/fixed_capacity/mod.rs b/src/containers/fixed_capacity/mod.rs index 5447aa2a..8d9dd3e0 100644 --- a/src/containers/fixed_capacity/mod.rs +++ b/src/containers/fixed_capacity/mod.rs @@ -11,6 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* +mod queue; mod vec; +pub use self::queue::FixedCapacityQueue; pub use self::vec::FixedCapacityVec; diff --git a/src/containers/fixed_capacity/queue.rs b/src/containers/fixed_capacity/queue.rs new file mode 100644 index 00000000..2024eebd --- /dev/null +++ b/src/containers/fixed_capacity/queue.rs @@ -0,0 +1,197 @@ +// ******************************************************************************* +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use core::ops; + +use crate::generic::queue::GenericQueue; +use crate::storage::Heap; + +/// A fixed-capacity queue. +/// +/// 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>, +} + +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 { + assert!(capacity <= u32::MAX as usize, "FixedQueue can hold at most u32::MAX elements"); + Self { + inner: GenericQueue::new(capacity as u32), + } + } +} + +impl Drop for FixedCapacityQueue { + fn drop(&mut self) { + self.inner.clear(); + } +} + +impl ops::Deref for FixedCapacityQueue { + type Target = GenericQueue>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl ops::DerefMut for FixedCapacityQueue { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +#[cfg(test)] +mod tests { + use std::collections::VecDeque; + + use super::*; + + fn to_vec((first, second): (&[T], &[T])) -> Vec { + let mut elements = first.to_vec(); + elements.extend_from_slice(second); + elements + } + + #[test] + fn push_back_and_pop_front() { + fn run_test(n: usize) { + let mut queue = FixedCapacityQueue::::new(n); + let mut control = VecDeque::new(); + + // Completely fill and empty the queue n times, but move the internal start point + // ahead by one each time + for _ in 0..n { + let result = queue.pop_front(); + assert_eq!(result, None); + + for i in 0..n { + let value = i as i64 * 123 + 456; + let result = queue.push_back(value); + assert_eq!(*result.unwrap(), value); + control.push_back(value); + assert_eq!(to_vec(queue.as_slices()), to_vec(control.as_slices())); + } + + let result = queue.push_back(123456); + assert!(result.is_err()); + + for _ in 0..n { + let expected = control.pop_front().unwrap(); + let actual = queue.pop_front(); + assert_eq!(actual, Some(expected)); + } + + let result = queue.pop_front(); + assert_eq!(result, None); + + // One push and one pop to move the internal start point ahead + queue.push_back(987).unwrap(); + assert_eq!(queue.pop_front(), Some(987)); + } + } + + for i in 0..6 { + run_test(i); + } + } + + #[test] + fn push_front_and_pop_back() { + fn run_test(n: usize) { + let mut queue = FixedCapacityQueue::::new(n); + let mut control = VecDeque::new(); + + // Completely fill and empty the queue n times, but move the internal start point + // ahead by one each time + for _ in 0..n { + let result = queue.pop_back(); + assert_eq!(result, None); + + for i in 0..n { + let value = i as i64 * 123 + 456; + let result = queue.push_front(value); + assert_eq!(*result.unwrap(), value); + control.push_front(value); + assert_eq!(to_vec(queue.as_slices()), to_vec(control.as_slices())); + } + + let result = queue.push_front(123456); + assert!(result.is_err()); + + for _ in 0..n { + let expected = control.pop_back().unwrap(); + let actual = queue.pop_back(); + assert_eq!(actual, Some(expected)); + } + + let result = queue.pop_back(); + assert_eq!(result, None); + + // One push and one pop to move the internal start point ahead + queue.push_front(987).unwrap(); + assert_eq!(queue.pop_back(), Some(987)); + } + } + + for i in 0..6 { + run_test(i); + } + } + + #[test] + fn is_empty_and_is_full() { + fn run_test(n: usize) { + let mut queue = FixedCapacityQueue::::new(n); + + // Completely fill and empty the queue n times, but move the internal start point + // ahead by one each time + for _ in 0..n { + assert!(queue.is_empty()); + + for i in 0..n { + assert!(!queue.is_full()); + queue.push_back(i as i64 * 123 + 456).unwrap(); + assert!(!queue.is_empty()); + } + + assert!(queue.is_full()); + + for _ in 0..n { + assert!(!queue.is_empty()); + queue.pop_front(); + assert!(!queue.is_full()); + } + + assert!(queue.is_empty()); + + // One push and one pop to move the internal start point ahead + queue.push_back(987).unwrap(); + assert_eq!(queue.pop_front(), Some(987)); + } + } + + for i in 0..6 { + run_test(i); + } + } +} diff --git a/src/containers/generic/mod.rs b/src/containers/generic/mod.rs index 3c2ac35f..db30d83c 100644 --- a/src/containers/generic/mod.rs +++ b/src/containers/generic/mod.rs @@ -11,4 +11,5 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* +pub(crate) mod queue; pub(crate) mod vec; diff --git a/src/containers/generic/queue.rs b/src/containers/generic/queue.rs new file mode 100644 index 00000000..3e6e3a76 --- /dev/null +++ b/src/containers/generic/queue.rs @@ -0,0 +1,352 @@ +// ******************************************************************************* +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use core::fmt; +use core::marker::PhantomData; +use core::mem::needs_drop; +use core::ops::Range; +use core::ptr; + +use crate::storage::Storage; + +#[repr(C)] +pub struct GenericQueue> { + /// The current number of elements in the queue. + len: u32, + /// The index of the next element to be returned by [`pop_front()`](Self::pop_front). + front_index: u32, + storage: S, + _marker: PhantomData, +} + +impl> GenericQueue { + /// Creates an empty queue. + pub fn new(capacity: u32) -> Self { + Self { + len: 0, + front_index: 0, + storage: S::new(capacity), + _marker: PhantomData, + } + } + + /// Extracts the slices containing the entire queue contents, in order. + /// + /// The caller should not make any assumptions about the distribution of the elements between + /// the two slices, except for ordering. + /// In particular, the first slice might be empty even though the second isn't. + /// + /// # Example + /// + /// ```ignore + /// let (first, second) = queue.as_slices(); + /// let elements: Vec<_> = std::iter::chain(first, second).collect(); + /// println!("Elements in queue: {elements:?}"); + /// ``` + pub fn as_slices(&self) -> (&[T], &[T]) { + let (first, second) = self.slice_ranges(); + let first = unsafe { &*self.storage.subslice(first.start, first.end) }; + let second = unsafe { &*self.storage.subslice(second.start, second.end) }; + (first, second) + } + + /// Extracts the slices containing the entire queue contents, in order. + /// + /// The caller should not make any assumptions about the distribution of the elements between + /// the two slices, except for ordering. + /// In particular, the first slice might be empty even though the second isn't. + /// + /// # Example + /// + /// ```ignore + /// let (first, second) = queue.as_mut_slices(); + /// for elements in std::iter::chain(first, second) { + /// *element *= 2; + /// } + /// ``` + pub fn as_mut_slices(&mut self) -> (&mut [T], &mut [T]) { + let (first, second) = self.slice_ranges(); + let first = unsafe { &mut *self.storage.subslice_mut(first.start, first.end) }; + let second = unsafe { &mut *self.storage.subslice_mut(second.start, second.end) }; + (first, second) + } + + /// Returns the maximum number of elements the queue can hold. + pub fn capacity(&self) -> usize { + self.storage.capacity() as usize + } + + /// Returns the current number of elements in the queue. + pub fn len(&self) -> usize { + self.len as usize + } + + /// Returns `true` if and only if the queue doesn't contain any elements. + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Returns `true` if and only if the queue has reached its capacity. + pub fn is_full(&self) -> bool { + self.len() == self.capacity() + } + + /// Tries to push an element to the back of the queue. + /// + /// If the queue has spare capacity, the push succeeds and a reference to that element + /// is returned; otherwise, `Err(QueueFull)` is returned. + pub fn push_back(&mut self, value: T) -> Result<&mut T, QueueFull> { + let capacity = self.storage.capacity(); + if self.len < capacity { + let write_pos = self.front_index as u64 + self.len as u64; + let write_pos = if write_pos < capacity as u64 { + write_pos as u32 + } else { + (write_pos - capacity as u64) as u32 + }; + self.len += 1; + Ok(unsafe { self.storage.element_mut(write_pos).write(value) }) + } else { + Err(QueueFull) + } + } + + /// Tries to push an element to the front of the queue. + /// + /// If the queue has spare capacity, the push succeeds and a reference to that element + /// is returned; otherwise, `Err(QueueFull)` is returned. + pub fn push_front(&mut self, value: T) -> Result<&mut T, QueueFull> { + let capacity = self.storage.capacity(); + if self.len < capacity { + let write_pos = if self.front_index > 0 { self.front_index - 1 } else { capacity - 1 }; + let element = unsafe { self.storage.element_mut(write_pos).write(value) }; + self.len += 1; + self.front_index = write_pos; + Ok(element) + } else { + Err(QueueFull) + } + } + + /// Tries to pop an element from the front of the queue. + /// + /// If the queue has at least one element, the pop succeeds; otherwise, `None` is returned. + pub fn pop_front(&mut self) -> Option { + if self.len > 0 { + let element = unsafe { self.storage.element(self.front_index).assume_init_read() }; + self.len -= 1; + if self.front_index < self.storage.capacity() - 1 { + self.front_index += 1; + } else { + self.front_index = 0; + } + Some(element) + } else { + None + } + } + + /// Tries to pop an element from the back of the queue. + /// + /// If the queue has at least one element, the pop succeeds; otherwise, `None` is returned. + pub fn pop_back(&mut self) -> Option { + let capacity = self.storage.capacity(); + if self.len > 0 { + let read_pos = self.front_index as u64 + (self.len as u64 - 1); + let read_pos = if read_pos < capacity as u64 { + read_pos as u32 + } else { + (read_pos - capacity as u64) as u32 + }; + self.len -= 1; + Some(unsafe { self.storage.element(read_pos).assume_init_read() }) + } else { + None + } + } + + /// Clears the queue, removing all values. + pub fn clear(&mut self) { + let (first, second) = self.slice_ranges(); + // Mark queue as empty before dropping elements, to prevent double-drop in case there's a panic in drop_in_place + self.len = 0; + self.front_index = 0; + if needs_drop::() { + unsafe { + ptr::drop_in_place(self.storage.subslice_mut(first.start, first.end)); + ptr::drop_in_place(self.storage.subslice_mut(second.start, second.end)); + } + } + } + + fn slice_ranges(&self) -> (Range, Range) { + // Cast to u64 to avoid overflow + let end = self.front_index as u64 + self.len as u64; + let capacity = self.storage.capacity(); + if end > capacity as u64 { + let end = (end - capacity as u64) as u32; + (self.front_index..capacity, 0..end) + } else { + let end = end as u32; + (self.front_index..end, end..end) + } + } +} + +/// Indicates that an operation failed because the queue would exceed its maximum capacity. +#[derive(Clone, Copy, Default, Debug)] +pub struct QueueFull; + +impl fmt::Display for QueueFull { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "queue is full") + } +} + +impl core::error::Error for QueueFull {} + +#[cfg(test)] +mod tests { + use std::{collections::VecDeque, mem::MaybeUninit}; + + use super::*; + + fn to_vec((first, second): (&[T], &[T])) -> Vec { + let mut elements = first.to_vec(); + elements.extend_from_slice(second); + elements + } + + #[test] + fn push_back_and_pop_front() { + fn run_test(n: usize) { + let mut queue = GenericQueue::>>::new(n as u32); + let mut control = VecDeque::new(); + + // Completely fill and empty the queue n times, but move the internal start point + // ahead by one each time + for _ in 0..n { + let result = queue.pop_front(); + assert_eq!(result, None); + + for i in 0..n { + let value = i as i64 * 123 + 456; + let result = queue.push_back(value); + assert_eq!(*result.unwrap(), value); + control.push_back(value); + assert_eq!(to_vec(queue.as_slices()), to_vec(control.as_slices())); + } + + let result = queue.push_back(123456); + assert!(result.is_err()); + + for _ in 0..n { + let expected = control.pop_front().unwrap(); + let actual = queue.pop_front(); + assert_eq!(actual, Some(expected)); + } + + let result = queue.pop_front(); + assert_eq!(result, None); + + // One push and one pop to move the internal start point ahead + queue.push_back(987).unwrap(); + assert_eq!(queue.pop_front(), Some(987)); + } + } + + for i in 0..6 { + run_test(i); + } + } + + #[test] + fn push_front_and_pop_back() { + fn run_test(n: usize) { + let mut queue = GenericQueue::>>::new(n as u32); + let mut control = VecDeque::new(); + + // Completely fill and empty the queue n times, but move the internal start point + // ahead by one each time + for _ in 0..n { + let result = queue.pop_back(); + assert_eq!(result, None); + + for i in 0..n { + let value = i as i64 * 123 + 456; + let result = queue.push_front(value); + assert_eq!(*result.unwrap(), value); + control.push_front(value); + assert_eq!(to_vec(queue.as_slices()), to_vec(control.as_slices())); + } + + let result = queue.push_front(123456); + assert!(result.is_err()); + + for _ in 0..n { + let expected = control.pop_back().unwrap(); + let actual = queue.pop_back(); + assert_eq!(actual, Some(expected)); + } + + let result = queue.pop_back(); + assert_eq!(result, None); + + // One push and one pop to move the internal start point ahead + queue.push_front(987).unwrap(); + assert_eq!(queue.pop_back(), Some(987)); + } + } + + for i in 0..6 { + run_test(i); + } + } + + #[test] + fn is_empty_and_is_full() { + fn run_test(n: usize) { + let mut queue = GenericQueue::>>::new(n as u32); + + // Completely fill and empty the queue n times, but move the internal start point + // ahead by one each time + for _ in 0..n { + assert!(queue.is_empty()); + + for i in 0..n { + assert!(!queue.is_full()); + queue.push_back(i as i64 * 123 + 456).unwrap(); + assert!(!queue.is_empty()); + } + + assert!(queue.is_full()); + + for _ in 0..n { + assert!(!queue.is_empty()); + queue.pop_front(); + assert!(!queue.is_full()); + } + + assert!(queue.is_empty()); + + // One push and one pop to move the internal start point ahead + queue.push_back(987).unwrap(); + assert_eq!(queue.pop_front(), Some(987)); + } + } + + for i in 0..6 { + run_test(i); + } + } +} diff --git a/src/containers/generic/vec.rs b/src/containers/generic/vec.rs index eba64c0a..dc4eb5a1 100644 --- a/src/containers/generic/vec.rs +++ b/src/containers/generic/vec.rs @@ -141,3 +141,73 @@ impl fmt::Display for VectorFull { } impl core::error::Error for VectorFull {} + +#[cfg(test)] +mod tests { + use std::mem::MaybeUninit; + + use super::*; + + #[test] + fn push_and_pop() { + fn run_test(n: usize) { + let mut vector = GenericVec::>>::new(n as u32); + let mut control = vec![]; + + let result = vector.pop(); + assert_eq!(result, None); + + for i in 0..n { + let value = i as i64 * 123 + 456; + let result = vector.push(value); + assert_eq!(*result.unwrap(), value); + control.push(value); + assert_eq!(vector.as_slice(), control.as_slice()); + } + + let result = vector.push(123456); + assert!(result.is_err()); + + for _ in 0..n { + let expected = control.pop().unwrap(); + let actual = vector.pop(); + assert_eq!(actual, Some(expected)); + } + + let result = vector.pop(); + assert_eq!(result, None); + } + + for i in 0..6 { + run_test(i); + } + } + + #[test] + fn is_full_and_is_empty() { + fn run_test(n: usize) { + let mut vector = GenericVec::>>::new(n as u32); + assert!(vector.is_empty()); + + for i in 0..n { + assert!(!vector.is_full()); + vector.push(i as i64 * 123 + 456).unwrap(); + assert!(!vector.is_empty()); + } + + assert!(vector.is_full()); + + for _ in 0..n { + assert!(!vector.is_empty()); + vector.pop(); + assert!(!vector.is_full()); + } + + assert!(vector.is_empty()); + } + + for i in 0..6 { + run_test(i); + } + } +} diff --git a/src/containers/inline/mod.rs b/src/containers/inline/mod.rs index 996fab67..c3cabdb5 100644 --- a/src/containers/inline/mod.rs +++ b/src/containers/inline/mod.rs @@ -11,6 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* +mod queue; mod vec; +pub use self::queue::InlineQueue; pub use self::vec::InlineVec; diff --git a/src/containers/inline/queue.rs b/src/containers/inline/queue.rs new file mode 100644 index 00000000..d8e03de9 --- /dev/null +++ b/src/containers/inline/queue.rs @@ -0,0 +1,216 @@ +// ******************************************************************************* +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use core::ops; + +use crate::generic::queue::GenericQueue; +use crate::storage::Inline; + +/// A fixed-capacity, ABI-compatible queue. +/// +/// The queue can hold between 0 and `CAPACITY` elements, and behaves similarly to Rust's `VecDeque`, +/// except that it stores the elements inline and doesn't allocate. +/// `CAPACITY` must be `>= 1` and `<= u32::MAX`. +/// +/// This data structure has a stable, well-defined memory layout and satisfies the requirements for +/// [ABI-compatible types](https://eclipse-score.github.io/score/main/features/communication/abi_compatible_data_types/index.html). +/// Its layout is structurally equivalent to: +/// +/// ```ignore +/// #[repr(C)] +/// struct Vec { +/// len: u32, +/// front_index: u32, +/// elements: [T; N], +/// } +/// ``` +#[repr(transparent)] +pub struct InlineQueue { + inner: GenericQueue>, +} + +impl InlineQueue { + const CHECK_CAPACITY: () = assert!(0 < CAPACITY && CAPACITY <= u32::MAX as usize); + + /// Creates an empty queue. + #[must_use] + pub fn new() -> Self { + let () = Self::CHECK_CAPACITY; + + Self { + inner: GenericQueue::new(CAPACITY as u32), + } + } +} + +impl Default for InlineQueue { + fn default() -> Self { + Self::new() + } +} + +impl ops::Deref for InlineQueue { + type Target = GenericQueue>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl ops::DerefMut for InlineQueue { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +#[cfg(test)] +mod tests { + use std::collections::VecDeque; + + use super::*; + + fn to_vec((first, second): (&[T], &[T])) -> Vec { + let mut elements = first.to_vec(); + elements.extend_from_slice(second); + elements + } + + #[test] + fn push_back_and_pop_front() { + fn run_test() { + let mut queue = InlineQueue::::new(); + let mut control = VecDeque::new(); + + // Completely fill and empty the queue N times, but move the internal start point + // ahead by one each time + for _ in 0..N { + let result = queue.pop_front(); + assert_eq!(result, None); + + for i in 0..N { + let value = i as i64 * 123 + 456; + let result = queue.push_back(value); + assert_eq!(*result.unwrap(), value); + control.push_back(value); + assert_eq!(to_vec(queue.as_slices()), to_vec(control.as_slices())); + } + + let result = queue.push_back(123456); + assert!(result.is_err()); + + for _ in 0..N { + let expected = control.pop_front().unwrap(); + let actual = queue.pop_front(); + assert_eq!(actual, Some(expected)); + } + + let result = queue.pop_front(); + assert_eq!(result, None); + + // One push and one pop to move the internal start point ahead + queue.push_back(987).unwrap(); + assert_eq!(queue.pop_front(), Some(987)); + } + } + + run_test::<1>(); + run_test::<2>(); + run_test::<3>(); + run_test::<4>(); + run_test::<5>(); + } + + #[test] + fn push_front_and_pop_back() { + fn run_test() { + let mut queue = InlineQueue::::new(); + let mut control = VecDeque::new(); + + // Completely fill and empty the queue N times, but move the internal start point + // ahead by one each time + for _ in 0..N { + let result = queue.pop_back(); + assert_eq!(result, None); + + for i in 0..N { + let value = i as i64 * 123 + 456; + let result = queue.push_front(value); + assert_eq!(*result.unwrap(), value); + control.push_front(value); + assert_eq!(to_vec(queue.as_slices()), to_vec(control.as_slices())); + } + + let result = queue.push_front(123456); + assert!(result.is_err()); + + for _ in 0..N { + let expected = control.pop_back().unwrap(); + let actual = queue.pop_back(); + assert_eq!(actual, Some(expected)); + } + + let result = queue.pop_back(); + assert_eq!(result, None); + + // One push and one pop to move the internal start point ahead + queue.push_front(987).unwrap(); + assert_eq!(queue.pop_back(), Some(987)); + } + } + + run_test::<1>(); + run_test::<2>(); + run_test::<3>(); + run_test::<4>(); + run_test::<5>(); + } + + #[test] + fn is_empty_and_is_full() { + fn run_test() { + let mut queue = InlineQueue::::new(); + + // Completely fill and empty the queue N times, but move the internal start point + // ahead by one each time + for _ in 0..N { + assert!(queue.is_empty()); + + for i in 0..N { + assert!(!queue.is_full()); + queue.push_back(i as i64 * 123 + 456).unwrap(); + assert!(!queue.is_empty()); + } + + assert!(queue.is_full()); + + for _ in 0..N { + assert!(!queue.is_empty()); + queue.pop_front(); + assert!(!queue.is_full()); + } + + assert!(queue.is_empty()); + + // One push and one pop to move the internal start point ahead + queue.push_back(987).unwrap(); + assert_eq!(queue.pop_front(), Some(987)); + } + } + + run_test::<1>(); + run_test::<2>(); + run_test::<3>(); + run_test::<4>(); + run_test::<5>(); + } +} diff --git a/src/containers/inline/vec.rs b/src/containers/inline/vec.rs index 2c8244f0..1d8d7d41 100644 --- a/src/containers/inline/vec.rs +++ b/src/containers/inline/vec.rs @@ -21,8 +21,19 @@ use crate::storage::Inline; /// /// The vector can hold between 0 and `CAPACITY` elements, and behaves similarly to Rust's `Vec`, /// except that it stores the elements inline and doesn't allocate. -/// /// `CAPACITY` must be `>= 1` and `<= u32::MAX`. +/// +/// This data structure has a stable, well-defined memory layout and satisfies the requirements for +/// [ABI-compatible types](https://eclipse-score.github.io/score/main/features/communication/abi_compatible_data_types/index.html). +/// Its layout is structurally equivalent to: +/// +/// ```ignore +/// #[repr(C)] +/// struct Vec { +/// len: u32, +/// elements: [T; N], +/// } +/// ``` #[repr(transparent)] pub struct InlineVec { inner: GenericVec>, diff --git a/src/containers/storage/mod.rs b/src/containers/storage/mod.rs index 031a0d69..aa4e0525 100644 --- a/src/containers/storage/mod.rs +++ b/src/containers/storage/mod.rs @@ -55,3 +55,36 @@ pub trait Storage { /// `start <= end <= self.capacity()` must hold. unsafe fn subslice_mut(&mut self, start: u32, end: u32) -> *mut [T]; } + +#[cfg(test)] +mod test_utils { + //! A simple impl of [`Storage`] for [`Vec`], to be used for tests of generic containers. + + use super::*; + + impl Storage for Vec> { + fn new(capacity: u32) -> Self { + (0..capacity).map(|_| MaybeUninit::zeroed()).collect() + } + + fn capacity(&self) -> u32 { + self.capacity() as u32 + } + + unsafe fn element(&self, index: u32) -> &MaybeUninit { + &self[index as usize] + } + + unsafe fn element_mut(&mut self, index: u32) -> &mut MaybeUninit { + &mut self[index as usize] + } + + unsafe fn subslice(&self, start: u32, end: u32) -> *const [T] { + &self[start as usize..end as usize] as *const [MaybeUninit] as *const [T] + } + + unsafe fn subslice_mut(&mut self, start: u32, end: u32) -> *mut [T] { + &mut self[start as usize..end as usize] as *mut [MaybeUninit] as *mut [T] + } + } +}