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