Skip to main content

iceoryx2_bb_container/string/
static_string.rs

1// Copyright (c) 2025 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13//! Relocatable (inter-process shared memory compatible) string implementations.
14//!
15//! The [`StaticString`](crate::string::StaticString) has a fixed capacity defined at compile time.
16//! It is memory-layout compatible to the C++ counterpart in the iceoryx2-bb-container C++ library
17//! and can be used for zero-copy cross-language communication.
18//!
19//! # Example
20//!
21//! ```
22//! # extern crate iceoryx2_bb_loggers;
23//!
24//! use iceoryx2_bb_container::string::*;
25//!
26//! const STRING_CAPACITY: usize = 123;
27//!
28//! let mut some_string = StaticString::<STRING_CAPACITY>::new();
29//! some_string.push_bytes(b"hello").unwrap();
30//! some_string.push('!' as u8).unwrap();
31//! some_string.push('!' as u8).unwrap();
32//!
33//! println!("removed byte {:?}", some_string.remove(0));
34//! ```
35
36use alloc::format;
37use core::str::FromStr;
38use core::{
39    cmp::Ordering,
40    fmt::{Debug, Display},
41    hash::Hash,
42    mem::MaybeUninit,
43    ops::{Deref, DerefMut},
44};
45use iceoryx2_bb_elementary_traits::atomic_copy::AtomicCopy;
46
47use iceoryx2_bb_derive_macros::{PlacementDefault, ZeroCopySend};
48use iceoryx2_bb_elementary::math::align_to;
49use iceoryx2_bb_elementary_traits::placement_default::PlacementDefault;
50use iceoryx2_bb_elementary_traits::zero_copy_send::ZeroCopySend;
51use iceoryx2_log::fail;
52use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
53
54use crate::string::{
55    String, StringModificationError, as_escaped_string, internal::StringView, strnlen,
56};
57
58/// Variant of the [`String`] that has a compile-time fixed capacity and is
59/// shared-memory compatible.
60#[derive(PlacementDefault, ZeroCopySend, Clone, Copy)]
61#[repr(C)]
62pub struct StaticString<const CAPACITY: usize> {
63    data: [MaybeUninit<u8>; CAPACITY],
64    terminator: u8,
65    len: u64,
66}
67
68unsafe impl<const CAPACITY: usize> AtomicCopy for StaticString<CAPACITY> {
69    fn __for_each_field<F: FnMut(usize, usize)>(&self, base_offset: usize, callback: &mut F) {
70        let aligned_base_offset = align_to::<Self>(base_offset);
71        callback(
72            aligned_base_offset + core::mem::offset_of!(Self, data),
73            size_of::<u8>() * self.len(),
74        );
75        callback(
76            aligned_base_offset + core::mem::offset_of!(Self, terminator),
77            size_of::<u8>(),
78        );
79        callback(
80            aligned_base_offset + core::mem::offset_of!(Self, len),
81            size_of::<u64>(),
82        );
83    }
84}
85
86impl<const CAPACITY: usize> Serialize for StaticString<CAPACITY> {
87    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
88    where
89        S: Serializer,
90    {
91        serializer.serialize_str(core::str::from_utf8(self.as_bytes()).unwrap())
92    }
93}
94
95struct StaticStringVisitor<const CAPACITY: usize>;
96
97impl<const CAPACITY: usize> Visitor<'_> for StaticStringVisitor<CAPACITY> {
98    type Value = StaticString<CAPACITY>;
99
100    fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
101        formatter.write_str(&format!("a string with a length of at most {CAPACITY}"))
102    }
103
104    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
105    where
106        E: serde::de::Error,
107    {
108        match StaticString::from_bytes(v.as_bytes()) {
109            Ok(v) => Ok(v),
110            Err(_) => Err(E::custom(format!(
111                "the string exceeds the maximum length of {CAPACITY}"
112            ))),
113        }
114    }
115}
116
117impl<'de, const CAPACITY: usize> Deserialize<'de> for StaticString<CAPACITY> {
118    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
119    where
120        D: Deserializer<'de>,
121    {
122        deserializer.deserialize_str(StaticStringVisitor)
123    }
124}
125
126unsafe impl<const CAPACITY: usize> Send for StaticString<CAPACITY> {}
127
128impl<const CAPACITY: usize, const CAPACITY_OTHER: usize> PartialOrd<StaticString<CAPACITY_OTHER>>
129    for StaticString<CAPACITY>
130{
131    fn partial_cmp(&self, other: &StaticString<CAPACITY_OTHER>) -> Option<Ordering> {
132        self.as_bytes().partial_cmp(other.as_bytes())
133    }
134}
135
136impl<const CAPACITY: usize> Ord for StaticString<CAPACITY> {
137    fn cmp(&self, other: &Self) -> Ordering {
138        self.as_bytes().cmp(other.as_bytes())
139    }
140}
141
142impl<const CAPACITY: usize> Hash for StaticString<CAPACITY> {
143    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
144        state.write(self.as_bytes())
145    }
146}
147
148impl<const CAPACITY: usize> Deref for StaticString<CAPACITY> {
149    type Target = [u8];
150
151    fn deref(&self) -> &Self::Target {
152        self.as_bytes()
153    }
154}
155
156impl<const CAPACITY: usize> DerefMut for StaticString<CAPACITY> {
157    fn deref_mut(&mut self) -> &mut Self::Target {
158        self.as_mut_bytes()
159    }
160}
161
162impl<const CAPACITY: usize, const OTHER_CAPACITY: usize> PartialEq<StaticString<OTHER_CAPACITY>>
163    for StaticString<CAPACITY>
164{
165    fn eq(&self, other: &StaticString<OTHER_CAPACITY>) -> bool {
166        *self.as_bytes() == *other.as_bytes()
167    }
168}
169
170impl<const CAPACITY: usize> Eq for StaticString<CAPACITY> {}
171
172impl<const CAPACITY: usize> PartialEq<&[u8]> for StaticString<CAPACITY> {
173    fn eq(&self, other: &&[u8]) -> bool {
174        *self.as_bytes() == **other
175    }
176}
177
178impl<const CAPACITY: usize> PartialEq<&str> for StaticString<CAPACITY> {
179    fn eq(&self, other: &&str) -> bool {
180        *self.as_bytes() == *other.as_bytes()
181    }
182}
183
184impl<const CAPACITY: usize> PartialEq<StaticString<CAPACITY>> for &str {
185    fn eq(&self, other: &StaticString<CAPACITY>) -> bool {
186        *self.as_bytes() == *other.as_bytes()
187    }
188}
189
190impl<const CAPACITY: usize, const OTHER_CAPACITY: usize> PartialEq<[u8; OTHER_CAPACITY]>
191    for StaticString<CAPACITY>
192{
193    fn eq(&self, other: &[u8; OTHER_CAPACITY]) -> bool {
194        *self.as_bytes() == *other
195    }
196}
197
198impl<const CAPACITY: usize, const OTHER_CAPACITY: usize> PartialEq<&[u8; OTHER_CAPACITY]>
199    for StaticString<CAPACITY>
200{
201    fn eq(&self, other: &&[u8; OTHER_CAPACITY]) -> bool {
202        *self.as_bytes() == **other
203    }
204}
205
206impl<const CAPACITY: usize> Debug for StaticString<CAPACITY> {
207    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208        write!(
209            f,
210            "StaticString<{}> {{ len: {}, data: \"{}\" }}",
211            CAPACITY,
212            self.len,
213            as_escaped_string(self.as_bytes())
214        )
215    }
216}
217
218impl<const CAPACITY: usize> Display for StaticString<CAPACITY> {
219    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
220        write!(f, "{}", as_escaped_string(self.as_bytes()))
221    }
222}
223
224impl<const CAPACITY: usize> TryFrom<&str> for StaticString<CAPACITY> {
225    type Error = StringModificationError;
226
227    fn try_from(value: &str) -> Result<Self, Self::Error> {
228        Self::try_from(value.as_bytes())
229    }
230}
231
232impl<const CAPACITY: usize, const N: usize> TryFrom<&[u8; N]> for StaticString<CAPACITY> {
233    type Error = StringModificationError;
234
235    fn try_from(value: &[u8; N]) -> Result<Self, Self::Error> {
236        Self::try_from(value.as_slice())
237    }
238}
239
240impl<const CAPACITY: usize> TryFrom<&[u8]> for StaticString<CAPACITY> {
241    type Error = StringModificationError;
242
243    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
244        if CAPACITY < value.len() {
245            fail!(from "StaticString::from<&[u8]>()",
246                with StringModificationError::InsertWouldExceedCapacity,
247                "The provided string \"{}\" does not fit into the StaticString with capacity {}",
248                as_escaped_string(value), CAPACITY);
249        }
250
251        let mut new_self = Self::new();
252        new_self.push_bytes(value)?;
253        Ok(new_self)
254    }
255}
256
257impl<const CAPACITY: usize> Default for StaticString<CAPACITY> {
258    fn default() -> Self {
259        Self::new()
260    }
261}
262
263impl<const CAPACITY: usize> StringView for StaticString<CAPACITY> {
264    fn data(&self) -> &[MaybeUninit<u8>] {
265        &self.data
266    }
267
268    unsafe fn data_mut(&mut self) -> &mut [MaybeUninit<u8>] {
269        &mut self.data
270    }
271
272    unsafe fn set_len(&mut self, len: u64) {
273        self.len = len
274    }
275}
276
277impl<const CAPACITY: usize> FromStr for StaticString<CAPACITY> {
278    type Err = StringModificationError;
279
280    fn from_str(s: &str) -> Result<Self, StringModificationError> {
281        Self::from_bytes(s.as_bytes())
282    }
283}
284
285impl<const CAPACITY: usize> StaticString<CAPACITY> {
286    /// Creates a new and empty [`StaticString`]
287    pub const fn new() -> Self {
288        let mut new_self = Self {
289            len: 0,
290            data: unsafe { MaybeUninit::uninit().assume_init() },
291            terminator: 0,
292        };
293        new_self.data[0] = MaybeUninit::new(0);
294        new_self
295    }
296
297    /// Creates a new [`StaticString`]. The user has to ensure that the string can hold the
298    /// bytes.
299    ///
300    /// # Safety
301    ///
302    ///  * `bytes` len must be smaller or equal than [`StaticString::capacity()`]
303    ///  * all unicode code points must be smaller 128 and not 0.
304    ///
305    pub const unsafe fn from_bytes_unchecked_restricted(bytes: &[u8], len: usize) -> Self {
306        debug_assert!(bytes.len() <= CAPACITY);
307        debug_assert!(len <= bytes.len());
308
309        let mut new_self = Self::new();
310        unsafe {
311            core::ptr::copy_nonoverlapping(bytes.as_ptr(), new_self.data.as_mut_ptr().cast(), len);
312            core::ptr::write::<u8>(new_self.data.as_mut_ptr().add(len).cast(), 0);
313        }
314        new_self.len = len as u64;
315        new_self
316    }
317
318    /// Creates a new [`StaticString`]. The user has to ensure that the string can hold the
319    /// bytes.
320    ///
321    /// # Safety
322    ///
323    ///  * `bytes` len must be smaller or equal than [`StaticString::capacity()`]
324    ///  * all unicode code points must be smaller 128 and not 0.
325    ///
326    pub const unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Self {
327        debug_assert!(bytes.len() <= CAPACITY);
328        unsafe { Self::from_bytes_unchecked_restricted(bytes, bytes.len()) }
329    }
330
331    /// Creates a new [`StaticString`] from a byte slice
332    pub fn from_bytes(bytes: &[u8]) -> Result<Self, StringModificationError> {
333        let mut new_self = Self::new();
334        new_self.insert_bytes(0, bytes)?;
335
336        Ok(new_self)
337    }
338
339    /// Creates a new [`StaticString`] from a byte slice. If the byte slice does not fit
340    /// into the [`StaticString`] it will be truncated.
341    pub fn from_bytes_truncated(bytes: &[u8]) -> Result<Self, StringModificationError> {
342        let mut new_self = Self::new();
343        new_self.insert_bytes(0, &bytes[0..core::cmp::min(bytes.len(), CAPACITY)])?;
344        Ok(new_self)
345    }
346
347    /// Creates a new [`StaticString`] from a string slice. If the string slice does not fit
348    /// into the [`StaticString`] it will be truncated.
349    pub fn from_str_truncated(s: &str) -> Result<Self, StringModificationError> {
350        Self::from_bytes_truncated(s.as_bytes())
351    }
352
353    /// Creates a new byte string from a given null-terminated string
354    ///
355    /// # Safety
356    ///
357    ///  * `ptr` must point to a valid memory position
358    ///  * `ptr` must be '\0' (null) terminated
359    ///
360    pub unsafe fn from_c_str(
361        ptr: *const core::ffi::c_char,
362    ) -> Result<Self, StringModificationError> {
363        let string_length = unsafe { strnlen(ptr, CAPACITY + 1) };
364        if CAPACITY < string_length {
365            return Err(StringModificationError::InsertWouldExceedCapacity);
366        }
367
368        Self::from_bytes(unsafe { core::slice::from_raw_parts(ptr.cast(), string_length) })
369    }
370
371    /// Returns the capacity of the [`StaticString`]
372    pub const fn capacity() -> usize {
373        CAPACITY
374    }
375
376    /// Returns a slice to the underlying bytes
377    pub const fn as_bytes_const(&self) -> &[u8] {
378        unsafe { core::slice::from_raw_parts(self.data.as_ptr().cast(), self.len as usize) }
379    }
380}
381
382impl<const CAPACITY: usize> String for StaticString<CAPACITY> {
383    fn capacity(&self) -> usize {
384        CAPACITY
385    }
386
387    fn len(&self) -> usize {
388        self.len as usize
389    }
390}
391
392#[allow(missing_docs)]
393pub struct StringMemoryLayoutMetrics {
394    pub string_size: usize,
395    pub string_alignment: usize,
396    pub size_data: usize,
397    pub offset_data: usize,
398    pub size_len: usize,
399    pub offset_len: usize,
400    pub len_is_unsigned: bool,
401}
402
403trait _StringMemoryLayoutFieldLenInspection {
404    fn is_unsigned(&self) -> bool;
405}
406
407impl _StringMemoryLayoutFieldLenInspection for u64 {
408    fn is_unsigned(&self) -> bool {
409        true
410    }
411}
412
413impl StringMemoryLayoutMetrics {
414    #[allow(missing_docs)]
415    pub fn from_string<const CAPACITY: usize>(v: &StaticString<CAPACITY>) -> Self {
416        StringMemoryLayoutMetrics {
417            string_size: core::mem::size_of_val(v),
418            string_alignment: core::mem::align_of_val(v),
419            size_data: core::mem::size_of_val(&v.data) + core::mem::size_of_val(&v.terminator),
420            offset_data: core::mem::offset_of!(StaticString<CAPACITY>, data),
421            size_len: core::mem::size_of_val(&v.len),
422            offset_len: core::mem::offset_of!(StaticString<CAPACITY>, len),
423            len_is_unsigned: v.len.is_unsigned(),
424        }
425    }
426}