Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/containers/fixed_capacity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
// *******************************************************************************

mod queue;
mod string;
mod vec;

pub use self::queue::FixedCapacityQueue;
pub use self::string::FixedCapacityString;
pub use self::vec::FixedCapacityVec;
191 changes: 191 additions & 0 deletions src/containers/fixed_capacity/string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// *******************************************************************************
// 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::ops;

use crate::generic::string::GenericString;
use crate::storage::Heap;

/// A fixed-capacity Unicode string.
///
/// 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<Heap<u8>>,
}

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 {
assert!(capacity <= u32::MAX as usize, "FixedCapacityString can hold at most u32::MAX bytes");
Self {
inner: GenericString::new(capacity as u32),
}
}

/// 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<Self> {
if capacity <= u32::MAX as usize {
Some(Self {
inner: GenericString::try_new(capacity as u32)?,
})
} else {
None
}
}
}

impl Drop for FixedCapacityString {
fn drop(&mut self) {
self.inner.clear();
}
}

impl ops::Deref for FixedCapacityString {
type Target = GenericString<Heap<u8>>;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl ops::DerefMut for FixedCapacityString {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}

impl fmt::Display for FixedCapacityString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}

impl fmt::Debug for FixedCapacityString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn push_and_pop() {
fn run_test(n: usize) {
let mut string = FixedCapacityString::new(n);
let mut control = String::new();

let result = string.pop();
assert_eq!(result, None);

let sample = "abcdefghi";
for ch in sample.chars().take(n) {
let result = string.push(ch);
assert!(result.is_ok());
control.push(ch);
assert_eq!(string.as_str(), control.as_str());
}

let result = string.push('x');
assert!(result.is_err());

for _ in 0..n {
let expected = control.pop().unwrap();
let actual = string.pop();
assert_eq!(actual, Some(expected));
}

let result = string.pop();
assert_eq!(result, None);
}

for i in 0..6 {
run_test(i);
}
}

#[test]
fn push_str() {
fn run_test(n: usize) {
let mut string = FixedCapacityString::new(n);
let mut control = String::new();

let samples = ["abc", "\0", "😉", "👍🏼🚀", "αβγ"];
for sample in samples {
let should_fit = string.capacity() - string.len() >= sample.len();
let result = string.push_str(sample);
if should_fit {
assert!(result.is_ok());
control.push_str(sample);
} else {
assert!(result.is_err());
}
assert_eq!(string.as_str(), control.as_str());
assert_eq!(string.len(), control.len());
assert_eq!(string.is_empty(), string.is_empty());
assert_eq!(string.is_full(), string.capacity() - string.len() == 0);
}
}

for i in [0, 1, 5, 20, 30] {
run_test(i);
}
}

#[test]
fn is_full_and_is_empty() {
fn run_test(n: usize) {
let mut string = FixedCapacityString::new(n);
assert!(string.is_empty());

let sample = "abcdefghi";
for ch in sample.chars().take(n) {
assert!(!string.is_full());
string.push(ch).unwrap();
assert!(!string.is_empty());
}

assert!(string.is_full());

for _ in 0..n {
assert!(!string.is_empty());
string.pop();
assert!(!string.is_full());
}

assert!(string.is_empty());
}

for i in 0..6 {
run_test(i);
}
}
}
14 changes: 14 additions & 0 deletions src/containers/fixed_capacity/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ impl<T> FixedCapacityVec<T> {
inner: GenericVec::new(capacity as u32),
}
}

/// 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<Self> {
if capacity <= u32::MAX as usize {
Some(Self {
inner: GenericVec::try_new(capacity as u32)?,
})
} else {
None
}
}
}

impl<T> Drop for FixedCapacityVec<T> {
Expand Down
1 change: 1 addition & 0 deletions src/containers/generic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
// *******************************************************************************

pub(crate) mod queue;
pub(crate) mod string;
pub(crate) mod vec;
26 changes: 7 additions & 19 deletions src/containers/generic/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
// 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::InsufficientCapacity;
use crate::storage::Storage;

#[repr(C)]
Expand Down Expand Up @@ -104,8 +104,8 @@ impl<T, S: Storage<T>> GenericQueue<T, S> {
/// 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> {
/// is returned; otherwise, `Err(InsufficientCapacity)` is returned.
pub fn push_back(&mut self, value: T) -> Result<&mut T, InsufficientCapacity> {
let capacity = self.storage.capacity();
if self.len < capacity {
let write_pos = self.front_index as u64 + self.len as u64;
Expand All @@ -117,15 +117,15 @@ impl<T, S: Storage<T>> GenericQueue<T, S> {
self.len += 1;
Ok(unsafe { self.storage.element_mut(write_pos).write(value) })
} else {
Err(QueueFull)
Err(InsufficientCapacity)
}
}

/// 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> {
/// is returned; otherwise, `Err(InsufficientCapacity)` is returned.
pub fn push_front(&mut self, value: T) -> Result<&mut T, InsufficientCapacity> {
let capacity = self.storage.capacity();
if self.len < capacity {
let write_pos = if self.front_index > 0 {
Expand All @@ -138,7 +138,7 @@ impl<T, S: Storage<T>> GenericQueue<T, S> {
self.front_index = write_pos;
Ok(element)
} else {
Err(QueueFull)
Err(InsufficientCapacity)
}
}

Expand Down Expand Up @@ -207,18 +207,6 @@ impl<T, S: Storage<T>> GenericQueue<T, S> {
}
}

/// 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};
Expand Down
Loading
Loading