Skip to main content

lib_unknown/types/
str.rs

1//! 定容 UTF-8 字符串容器:栈上(`StackStr`)与堆上(`HeapStr`,需 `alloc`)实现,写入时校验编码,源缓冲按需擦除。
2//!
3//! 需要启用 `"types-str"` 特性。
4#![allow(unused_qualifications)]
5#![allow(clippy::similar_names)]
6#![allow(unused)]
7
8#[cfg(feature = "alloc")]
9extern crate alloc;
10
11#[cfg(feature = "alloc")]
12use super::bytes::HeapBytes;
13use super::bytes::{Bytes, BytesError, BytesResult, StackBytes, volatile_zero};
14use core::convert::TryFrom;
15use core::str::Utf8Error;
16
17/// 安全字符串的错误类型。
18///
19/// # Feature Requirement
20///
21/// 需要启用 `"types-str"` 特性。
22///
23/// # Examples
24///
25/// ```rust
26/// use core::convert::TryInto;
27/// use lib_unknown::types::str::{StackStr, StrError};
28///
29/// let res: Result<StackStr<2>, _> = "toolong".try_into();
30/// assert!(matches!(res, Err(StrError::CapacityExceeded(_))));
31/// ```
32#[derive(Debug)]
33pub enum StrError {
34    /// 长度超出容器容量。
35    CapacityExceeded(BytesError),
36    /// 源内容不是合法 UTF-8(源仍会被清零)。
37    InvalidUtf8(Utf8Error),
38    /// 收到空指针。
39    NullPointer,
40}
41
42impl core::error::Error for StrError {}
43
44impl core::fmt::Display for StrError {
45    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46        match self {
47            Self::CapacityExceeded(e) => write!(f, "capacity exceeded: {e}"),
48            Self::InvalidUtf8(e) => write!(f, "invalid UTF-8 sequence: {e}"),
49            Self::NullPointer => write!(f, "received null pointer"),
50        }
51    }
52}
53
54#[inline(always)]
55fn safe_parse_and_wipe<F>(buffer: &mut [u8], mut push_fn: F) -> Result<(), StrError>
56where
57    F: FnMut(&str) -> Result<(), StrError>,
58{
59    let res = match ::core::str::from_utf8(buffer) {
60        Ok(valid_str) => push_fn(valid_str),
61        Err(e) => Err(StrError::InvalidUtf8(e)),
62    };
63    volatile_zero(buffer);
64    res
65}
66
67/// 安全字符串的统一接口:只读视图,内部恒为合法 UTF-8。
68///
69/// # Feature Requirement
70///
71/// 需要启用 `"types-str"` 特性。
72///
73/// # Examples
74///
75/// ```rust
76/// use core::convert::TryInto;
77/// use lib_unknown::types::str::{StackStr, Str};
78///
79/// let s: StackStr<8> = "hi".try_into().unwrap();
80/// assert_eq!(s.as_str(), "hi");
81/// assert_eq!(s.len(), 2);
82/// ```
83pub trait Str:
84    ::core::ops::Deref<Target = str> + ::core::fmt::Display + ::core::fmt::Debug + Send + Sync
85{
86    /// 以 `&str` 形式查看内容。
87    ///
88    /// # Feature Requirement
89    ///
90    /// 需要启用 `"types-str"` 特性。
91    fn as_str(&self) -> &str;
92
93    /// 以字节切片查看内容。
94    ///
95    /// # Feature Requirement
96    ///
97    /// 需要启用 `"types-str"` 特性。
98    #[inline(always)]
99    fn as_bytes(&self) -> &[u8] {
100        self.as_str().as_bytes()
101    }
102
103    /// 类型擦除的堆克隆,用于 `Box<dyn Str>` 容器。
104    ///
105    /// # Feature Requirement
106    ///
107    /// 需要启用 `"types-str"` 与 `"alloc"` 特性。
108    #[cfg(feature = "alloc")]
109    fn dyn_clone(&self) -> alloc::boxed::Box<dyn Str>;
110
111    /// 返回内容的字节长度。
112    ///
113    /// # Feature Requirement
114    ///
115    /// 需要启用 `"types-str"` 特性。
116    #[inline(always)]
117    fn len(&self) -> usize {
118        self.as_str().len()
119    }
120
121    /// 内容是否为空。
122    ///
123    /// # Feature Requirement
124    ///
125    /// 需要启用 `"types-str"` 特性。
126    #[inline(always)]
127    fn is_empty(&self) -> bool {
128        self.as_str().is_empty()
129    }
130}
131
132/// 栈上定容安全字符串,`Drop` 时自动清零,适用于短口令、令牌等小敏感文本。
133///
134/// # Feature Requirement
135///
136/// 需要启用 `"types-str"` 特性。
137///
138/// # Examples
139///
140/// ```rust
141/// use core::convert::TryInto;
142/// use lib_unknown::types::str::StackStr;
143///
144/// let s: StackStr<8> = "hi".try_into().unwrap();
145/// assert_eq!(&*s, "hi");
146/// ```
147pub struct StackStr<const N: usize>(StackBytes<N>);
148
149/// 堆上定容安全字符串,语义与 [`StackStr`] 一致,适用于较长的敏感文本。
150///
151/// # Feature Requirement
152///
153/// 需要启用 `"types-str"` 与 `"alloc"` 特性。
154///
155/// # Examples
156///
157/// ```rust
158/// use core::convert::TryInto;
159/// use lib_unknown::types::str::HeapStr;
160///
161/// let s: HeapStr<16> = "hi".try_into().unwrap();
162/// assert_eq!(&*s, "hi");
163/// ```
164#[cfg(feature = "alloc")]
165pub struct HeapStr<const N: usize>(HeapBytes<N>);
166
167impl<const N: usize> StackStr<N> {
168    /// 容器容量(字节)。
169    ///
170    /// # Feature Requirement
171    ///
172    /// 需要启用 `"types-str"` 特性。
173    pub const CAPACITY: usize = N;
174    /// 创建空字符串。
175    ///
176    /// # Feature Requirement
177    ///
178    /// 需要启用 `"types-str"` 特性。
179    #[inline(always)]
180    pub const fn new() -> Self {
181        Self(StackBytes::new())
182    }
183}
184
185#[cfg(feature = "alloc")]
186impl<const N: usize> HeapStr<N> {
187    /// 容器容量(字节)。
188    ///
189    /// # Feature Requirement
190    ///
191    /// 需要启用 `"types-str"` 与 `"alloc"` 特性。
192    pub const CAPACITY: usize = N;
193    /// 创建空字符串。
194    ///
195    /// # Feature Requirement
196    ///
197    /// 需要启用 `"types-str"` 与 `"alloc"` 特性。
198    #[inline(always)]
199    pub fn new() -> Self {
200        Self(HeapBytes::new())
201    }
202}
203
204macro_rules! impl_secure_str_base {
205    ($name:ident) => {
206        impl<const N: usize> $name<N> {
207            /// 从原始可变指针拷贝并擦除源缓冲构造安全字符串。
208            ///
209            /// 成功时将源 `len` 字节按 UTF-8 解析后拷贝并清零源内存。
210            ///
211            /// # Safety
212            ///
213            /// 调用此函数必须保证:
214            /// 1. `ptr` 非空且在 `len` 范围内可读写、正确对齐;
215            /// 2. 调用期间该内存不被其他线程并发访问。
216            ///
217            /// # Errors
218            ///
219            /// - `ptr` 为空时返回 [`StrError::NullPointer`]。
220            /// - 源内容非合法 UTF-8 时返回 [`StrError::InvalidUtf8`](源仍会被清零)。
221            /// - 长度超出 `N` 时返回 [`StrError::CapacityExceeded`]。
222            pub unsafe fn from_raw_parts_mut(ptr: *mut u8, len: usize) -> Result<Self, StrError> {
223                if ptr.is_null() {
224                    return Err(StrError::NullPointer);
225                }
226                if len == 0 {
227                    return Ok(Self::new());
228                }
229
230                // SAFETY: `ptr` 非空已检查;有效性、对齐与独占性由本函数的 `# Safety` 约定保证。
231                let slice = unsafe { ::core::slice::from_raw_parts_mut(ptr, len) };
232
233                let mut out = Self::new();
234                safe_parse_and_wipe(slice, |s| out.try_push_str(s))?;
235                Ok(out)
236            }
237
238            #[inline(always)]
239            fn try_push_str(&mut self, s: &str) -> Result<(), StrError> {
240                self.0
241                    .extend_from_slice(s.as_bytes())
242                    .map_err(StrError::CapacityExceeded)
243            }
244
245            /// 将内容搬运到容量为 `M` 的同类字符串中。
246            ///
247            /// # Feature Requirement
248            ///
249            /// 需要启用 `"types-str"` 特性(`HeapStr` 相关还需 `"alloc"` 特性)。
250            ///
251            /// # Examples
252            ///
253            /// ```rust
254            /// use core::convert::TryInto;
255            /// use lib_unknown::types::str::StackStr;
256            ///
257            /// let s: StackStr<4> = "hi".try_into().unwrap();
258            /// let c = s.try_grow::<8>().unwrap();
259            /// assert_eq!(&*c, "hi");
260            /// ```
261            ///
262            /// # Errors
263            ///
264            /// - 当已有长度超出 `M` 时返回 [`StrError::CapacityExceeded`]。
265            ///
266            /// # Panics
267            ///
268            /// - 内部 `expect("Capacity already checked")`:前置长度检查保证不触发,仅防御性保留。
269            #[inline]
270            pub fn try_grow<const M: usize>(self) -> Result<$name<M>, StrError> {
271                if M < self.len() {
272                    return Err(StrError::CapacityExceeded(BytesError::CapacityExceeded {
273                        requested: self.len(),
274                        max: M,
275                    }));
276                }
277                let mut larger = $name::<M>::new();
278                larger
279                    .0
280                    .extend_from_slice(self.as_bytes())
281                    .expect("Capacity already checked");
282                Ok(larger)
283            }
284
285            /// 将自身与 `suffix` 拼接为容量 `M` 的新字符串。
286            ///
287            /// # Feature Requirement
288            ///
289            /// 需要启用 `"types-str"` 特性(`HeapStr` 相关还需 `"alloc"` 特性)。
290            ///
291            /// # Examples
292            ///
293            /// ```rust
294            /// use core::convert::TryInto;
295            /// use lib_unknown::types::str::StackStr;
296            ///
297            /// let s: StackStr<4> = "hi".try_into().unwrap();
298            /// let c = s.push_str_into::<8>("!").unwrap();
299            /// assert_eq!(&*c, "hi!");
300            /// ```
301            ///
302            /// # Errors
303            ///
304            /// - 当拼接后长度超出 `M` 时返回 [`StrError::CapacityExceeded`]。
305            ///
306            /// # Panics
307            ///
308            /// - 两处内部 `unwrap`:前置长度检查保证不触发,仅防御性保留。
309            #[inline]
310            pub fn push_str_into<const M: usize>(self, suffix: &str) -> Result<$name<M>, StrError> {
311                let required_len = self.len() + suffix.len();
312                if required_len > M {
313                    return Err(StrError::CapacityExceeded(BytesError::CapacityExceeded {
314                        requested: required_len,
315                        max: M,
316                    }));
317                }
318                let mut larger = $name::<M>::new();
319                larger.0.extend_from_slice(self.as_bytes()).unwrap();
320                larger.0.extend_from_slice(suffix.as_bytes()).unwrap();
321                Ok(larger)
322            }
323
324            /// 将自身与 `rhs` 拼接为容量 `M` 的新字符串(`rhs` 为任意可借用为 `str` 的类型)。
325            ///
326            /// # Feature Requirement
327            ///
328            /// 需要启用 `"types-str"` 特性(`HeapStr` 相关还需 `"alloc"` 特性)。
329            ///
330            /// # Examples
331            ///
332            /// ```rust
333            /// use core::convert::TryInto;
334            /// use lib_unknown::types::str::StackStr;
335            ///
336            /// let s: StackStr<4> = "hi".try_into().unwrap();
337            /// let c = s.concat_into::<8>("!").unwrap();
338            /// assert_eq!(&*c, "hi!");
339            /// ```
340            ///
341            /// # Errors
342            ///
343            /// - 当拼接后长度超出 `M` 时返回 [`StrError::CapacityExceeded`]。
344            pub fn concat_into<const M: usize>(
345                self,
346                rhs: impl ::core::convert::AsRef<str>,
347            ) -> Result<$name<M>, StrError> {
348                let mut new_buf = $name::<M>::new();
349                new_buf.try_push_str(self.as_str())?;
350                new_buf.try_push_str(rhs.as_ref())?;
351                Ok(new_buf)
352            }
353        }
354
355        impl<const N: usize> ::core::default::Default for $name<N> {
356            #[inline(always)]
357            fn default() -> Self {
358                Self::new()
359            }
360        }
361
362        impl<const N: usize> Str for $name<N> {
363            #[inline(always)]
364            fn as_str(&self) -> &str {
365                // SAFETY: 类型不变式保证内部字节恒为合法 UTF-8(经 `try_push_str` 校验写入)。
366                unsafe { ::core::str::from_utf8_unchecked(self.0.as_slice()) }
367            }
368
369            #[cfg(feature = "alloc")]
370            #[inline(always)]
371            fn dyn_clone(&self) -> alloc::boxed::Box<dyn Str> {
372                alloc::boxed::Box::new(self.clone())
373            }
374        }
375
376        impl<const N: usize> ::core::clone::Clone for $name<N> {
377            #[inline(always)]
378            fn clone(&self) -> Self {
379                let mut new_str = Self::new();
380                let _ = new_str.0.extend_from_slice(self.as_bytes());
381                new_str
382            }
383        }
384    };
385}
386
387macro_rules! impl_try_from_traits {
388    ($name:ident) => {
389        impl<const N: usize> TryFrom<&str> for $name<N> {
390            type Error = StrError;
391            #[inline(always)]
392            fn try_from(s: &str) -> Result<Self, Self::Error> {
393                let mut out = Self::new();
394                out.try_push_str(s)?;
395                Ok(out)
396            }
397        }
398
399        impl<const N: usize> ::core::str::FromStr for $name<N> {
400            type Err = StrError;
401            #[inline(always)]
402            fn from_str(s: &str) -> Result<Self, Self::Err> {
403                Self::try_from(s)
404            }
405        }
406
407        impl<const N: usize> TryFrom<&mut [u8; N]> for $name<N> {
408            type Error = StrError;
409            fn try_from(arr: &mut [u8; N]) -> Result<Self, Self::Error> {
410                let mut out = Self::new();
411                safe_parse_and_wipe(arr, |s| out.try_push_str(s))?;
412                Ok(out)
413            }
414        }
415
416        impl<const N: usize> TryFrom<&mut [u8]> for $name<N> {
417            type Error = StrError;
418            fn try_from(arr: &mut [u8]) -> Result<Self, Self::Error> {
419                let mut out = Self::new();
420                safe_parse_and_wipe(arr, |s| out.try_push_str(s))?;
421                Ok(out)
422            }
423        }
424
425        #[cfg(feature = "alloc")]
426        impl<const N: usize> TryFrom<alloc::boxed::Box<[u8; N]>> for $name<N> {
427            type Error = StrError;
428            fn try_from(mut b: alloc::boxed::Box<[u8; N]>) -> Result<Self, Self::Error> {
429                let mut out = Self::new();
430                safe_parse_and_wipe(&mut *b, |s| out.try_push_str(s))?;
431                Ok(out)
432            }
433        }
434
435        #[cfg(feature = "alloc")]
436        impl<const N: usize> TryFrom<alloc::vec::Vec<u8>> for $name<N> {
437            type Error = StrError;
438            fn try_from(mut v: alloc::vec::Vec<u8>) -> Result<Self, Self::Error> {
439                let mut out = Self::new();
440                safe_parse_and_wipe(v.as_mut_slice(), |s| out.try_push_str(s))?;
441                Ok(out)
442            }
443        }
444
445        #[cfg(feature = "alloc")]
446        impl<const N: usize> TryFrom<alloc::string::String> for $name<N> {
447            type Error = StrError;
448            #[inline(always)]
449            fn try_from(s: alloc::string::String) -> Result<Self, Self::Error> {
450                Self::try_from(s.into_bytes())
451            }
452        }
453    };
454}
455
456macro_rules! __impl_common_str_traits {
457    ($name:ident) => {
458        impl<const N: usize> ::core::ops::Deref for $name<N> {
459            type Target = str;
460            #[inline(always)]
461            fn deref(&self) -> &Self::Target {
462                self.as_str()
463            }
464        }
465        impl<const N: usize> ::core::fmt::Display for $name<N> {
466            #[inline(always)]
467            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
468                f.write_str(self.as_str())
469            }
470        }
471        impl<const N: usize> ::core::fmt::Debug for $name<N> {
472            #[inline(always)]
473            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
474                ::core::fmt::Debug::fmt(self.as_str(), f)
475            }
476        }
477        impl<const N: usize> ::core::convert::AsRef<str> for $name<N> {
478            #[inline(always)]
479            fn as_ref(&self) -> &str {
480                self.as_str()
481            }
482        }
483        impl<const N: usize> ::core::convert::AsRef<[u8]> for $name<N> {
484            #[inline(always)]
485            fn as_ref(&self) -> &[u8] {
486                self.as_bytes()
487            }
488        }
489        impl<const N: usize> ::core::cmp::PartialEq<str> for $name<N> {
490            #[inline(always)]
491            fn eq(&self, other: &str) -> bool {
492                self.as_str() == other
493            }
494        }
495        impl<'a, const N: usize> ::core::cmp::PartialEq<&'a str> for $name<N> {
496            #[inline(always)]
497            fn eq(&self, other: &&'a str) -> bool {
498                self.as_str() == *other
499            }
500        }
501        impl<const N: usize, const M: usize> ::core::cmp::PartialEq<$name<M>> for $name<N> {
502            #[inline(always)]
503            fn eq(&self, other: &$name<M>) -> bool {
504                self.as_str() == other.as_str()
505            }
506        }
507        #[cfg(feature = "alloc")]
508        impl<const N: usize> ::core::cmp::PartialEq<alloc::string::String> for $name<N> {
509            #[inline(always)]
510            fn eq(&self, other: &alloc::string::String) -> bool {
511                self.as_str() == other.as_str()
512            }
513        }
514        #[cfg(feature = "alloc")]
515        impl<const N: usize> ::core::cmp::PartialEq<$name<N>> for alloc::string::String {
516            #[inline(always)]
517            fn eq(&self, other: &$name<N>) -> bool {
518                self.as_str() == other.as_str()
519            }
520        }
521        impl<const N: usize> ::core::cmp::Eq for $name<N> {}
522        impl<const N: usize> ::core::cmp::PartialOrd for $name<N> {
523            #[inline(always)]
524            fn partial_cmp(&self, other: &Self) -> ::core::option::Option<::core::cmp::Ordering> {
525                Some(self.cmp(other))
526            }
527        }
528        impl<const N: usize> ::core::cmp::Ord for $name<N> {
529            #[inline(always)]
530            fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
531                self.as_str().cmp(other.as_str())
532            }
533        }
534        impl<const N: usize> ::core::hash::Hash for $name<N> {
535            #[inline(always)]
536            fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
537                self.as_str().hash(state)
538            }
539        }
540        impl<const N: usize> ::core::borrow::Borrow<str> for $name<N> {
541            #[inline(always)]
542            fn borrow(&self) -> &str {
543                self.as_str()
544            }
545        }
546    };
547}
548
549impl_secure_str_base!(StackStr);
550impl_try_from_traits!(StackStr);
551__impl_common_str_traits!(StackStr);
552
553#[cfg(feature = "alloc")]
554impl_secure_str_base!(HeapStr);
555#[cfg(feature = "alloc")]
556impl_try_from_traits!(HeapStr);
557#[cfg(feature = "alloc")]
558__impl_common_str_traits!(HeapStr);
559
560#[cfg(feature = "alloc")]
561impl<const N: usize> HeapStr<N> {
562    /// 将内容复制为普通 `String`(敏感内存外泄,谨慎使用,已废弃)。
563    ///
564    /// # Feature Requirement
565    ///
566    /// 需要启用 `"types-str"` 与 `"alloc"` 特性。
567    #[inline(always)]
568    #[deprecated(note = "This leaks secure memory into alloc::string::String. Use with caution.")]
569    pub fn leak_into_string(self) -> alloc::string::String {
570        use alloc::borrow::ToOwned;
571        self.as_str().to_owned()
572    }
573}
574
575#[cfg(feature = "alloc")]
576impl<const N: usize> StackStr<N> {
577    /// 将栈字符串搬运为容量 `M` 的堆字符串。
578    ///
579    /// # Feature Requirement
580    ///
581    /// 需要启用 `"types-str"` 与 `"alloc"` 特性。
582    ///
583    /// # Examples
584    ///
585    /// ```rust
586    /// use core::convert::TryInto;
587    /// use lib_unknown::types::str::StackStr;
588    ///
589    /// let s: StackStr<4> = "hi".try_into().unwrap();
590    /// let h = s.into_heap::<8>().unwrap();
591    /// assert_eq!(&*h, "hi");
592    /// ```
593    ///
594    /// # Errors
595    ///
596    /// - 当已有长度超出 `M` 时返回 [`StrError::CapacityExceeded`]。
597    ///
598    /// # Panics
599    ///
600    /// - 内部 `expect("Capacity checked")`:前置长度检查保证不触发,仅防御性保留。
601    #[inline]
602    pub fn into_heap<const M: usize>(self) -> Result<HeapStr<M>, StrError> {
603        if M < self.len() {
604            return Err(StrError::CapacityExceeded(BytesError::CapacityExceeded {
605                requested: self.len(),
606                max: M,
607            }));
608        }
609        let mut heap_str = HeapStr::<M>::new();
610        heap_str
611            .0
612            .extend_from_slice(self.as_bytes())
613            .expect("Capacity checked");
614        Ok(heap_str)
615    }
616}
617
618#[cfg(feature = "alloc")]
619impl<const N: usize, const M: usize> ::core::cmp::PartialEq<StackStr<M>> for HeapStr<N> {
620    #[inline(always)]
621    fn eq(&self, other: &StackStr<M>) -> bool {
622        self.as_str() == other.as_str()
623    }
624}
625#[cfg(feature = "alloc")]
626impl<const N: usize, const M: usize> ::core::cmp::PartialEq<HeapStr<N>> for StackStr<M> {
627    #[inline(always)]
628    fn eq(&self, other: &HeapStr<N>) -> bool {
629        self.as_str() == other.as_str()
630    }
631}
632
633#[cfg(feature = "alloc")]
634impl ::core::clone::Clone for alloc::boxed::Box<dyn Str> {
635    #[inline(always)]
636    fn clone(&self) -> Self {
637        self.dyn_clone()
638    }
639}
640#[cfg(feature = "alloc")]
641impl ::core::cmp::PartialEq for alloc::boxed::Box<dyn Str> {
642    #[inline(always)]
643    fn eq(&self, other: &Self) -> bool {
644        self.as_str() == other.as_str()
645    }
646}
647#[cfg(feature = "alloc")]
648impl ::core::cmp::Eq for alloc::boxed::Box<dyn Str> {}
649#[cfg(feature = "alloc")]
650impl ::core::cmp::PartialEq<str> for alloc::boxed::Box<dyn Str> {
651    #[inline(always)]
652    fn eq(&self, other: &str) -> bool {
653        self.as_str() == other
654    }
655}
656#[cfg(feature = "alloc")]
657impl ::core::cmp::PartialEq<&str> for alloc::boxed::Box<dyn Str> {
658    #[inline(always)]
659    fn eq(&self, other: &&str) -> bool {
660        self.as_str() == *other
661    }
662}
663#[cfg(feature = "alloc")]
664impl ::core::cmp::PartialEq<alloc::string::String> for alloc::boxed::Box<dyn Str> {
665    #[inline(always)]
666    fn eq(&self, other: &alloc::string::String) -> bool {
667        self.as_str() == other.as_str()
668    }
669}
670
671#[cfg(test)]
672#[allow(clippy::unwrap_used, clippy::expect_used)]
673mod tests {
674    use super::*;
675    #[cfg(feature = "alloc")]
676    use alloc::{boxed::Box, vec, vec::Vec};
677    use core::convert::TryInto;
678
679    #[test]
680    fn test_stack_from_str_literal() {
681        let s: StackStr<8> = "hello".try_into().unwrap();
682        assert_eq!(s, "hello");
683    }
684
685    #[cfg(feature = "alloc")]
686    #[test]
687    fn test_heap_from_str_literal() {
688        let h: HeapStr<16> = "hello".try_into().unwrap();
689        assert_eq!(h, "hello");
690    }
691
692    #[cfg(feature = "alloc")]
693    #[test]
694    fn test_vec_dyn_str() {
695        let mut secret_list: Vec<Box<dyn Str>> = Vec::new();
696
697        let h: HeapStr<256> = "SuperSecret_Heap".try_into().unwrap();
698        secret_list.push(Box::new(h));
699
700        let s: StackStr<16> = "Short_Stack".try_into().unwrap();
701        secret_list.push(Box::new(s));
702
703        let s2: StackStr<32> = "Another_Stack_32".try_into().unwrap();
704        secret_list.push(Box::new(s2));
705
706        assert_eq!(secret_list.len(), 3);
707        assert!(secret_list[0].starts_with("Super"));
708        assert!(secret_list[1].ends_with("Stack"));
709    }
710
711    #[cfg(feature = "alloc")]
712    #[test]
713    fn from_raw_ptr_with_wiping() {
714        let mut source_data = b"secret_data".to_vec();
715        let ptr = source_data.as_mut_ptr();
716        let len = source_data.len();
717
718        let h = unsafe { HeapStr::<32>::from_raw_parts_mut(ptr, len) }.unwrap();
719        assert_eq!(h.as_str(), "secret_data");
720
721        assert_eq!(source_data, vec![0u8; 11]);
722    }
723
724    #[test]
725    fn test_capacity_exceeded_fail_fast() {
726        let res: Result<StackStr<4>, _> = "hello".try_into();
727        assert!(res.is_err());
728    }
729
730    #[test]
731    fn test_invalid_utf8_wiping() {
732        let mut bad_utf8 = [0, 159, 146, 150];
733        let res = StackStr::<16>::try_from(bad_utf8.as_mut_slice());
734        assert!(res.is_err());
735        assert_eq!(bad_utf8, [0u8; 4]);
736    }
737}