use core::mem::ManuallyDrop;
use std::borrow::{Borrow, BorrowMut};
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
pub trait AnyString {
fn as_str(&self) -> &str;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn push_str(&mut self, s: &str);
fn push(&mut self, ch: char);
fn clear(&mut self);
fn pop(&mut self) -> Option<char>;
fn truncate(&mut self, new_len: usize);
}
impl AnyString for String {
fn as_str(&self) -> &str {
self.as_str()
}
fn len(&self) -> usize {
self.len()
}
fn push_str(&mut self, s: &str) {
self.push_str(s);
}
fn push(&mut self, ch: char) {
self.push(ch);
}
fn clear(&mut self) {
self.clear();
}
fn pop(&mut self) -> Option<char> {
self.pop()
}
fn truncate(&mut self, new_len: usize) {
self.truncate(new_len);
}
}
pub struct SmallString<const N: usize> {
on_stack: bool,
data: StringData<N>,
}
impl<const N: usize> AnyString for SmallString<N> {
fn as_str(&self) -> &str {
self.as_str()
}
fn len(&self) -> usize {
self.len()
}
fn push_str(&mut self, s: &str) {
self.push_str(s);
}
fn push(&mut self, ch: char) {
self.push(ch);
}
fn clear(&mut self) {
self.clear();
}
fn pop(&mut self) -> Option<char> {
self.pop()
}
fn truncate(&mut self, new_len: usize) {
self.truncate(new_len);
}
}
union StringData<const N: usize> {
stack: ManuallyDrop<heapless::String<N>>,
heap: ManuallyDrop<std::string::String>,
}
impl<const N: usize> SmallString<N> {
pub const MAX_STACK_SIZE: usize = 16 * 1024;
pub fn new() -> Self {
const {
assert!(
std::mem::size_of::<Self>() <= Self::MAX_STACK_SIZE,
"SmallString is too large! The struct size exceeds the 16KB limit. Reduce N."
);
}
Self {
on_stack: true,
data: StringData {
stack: ManuallyDrop::new(heapless::String::new()),
},
}
}
pub fn from_str(s: &str) -> Self {
let mut str = Self::new();
str.push_str(s);
str
}
#[inline(always)]
pub fn is_on_stack(&self) -> bool {
self.on_stack
}
#[inline(always)]
pub fn push(&mut self, ch: char) {
unsafe {
if self.on_stack {
let stack_str = &mut *self.data.stack;
if stack_str.len() + ch.len_utf8() > N {
self.spill_to_heap_and_push_char(ch);
} else {
match stack_str.push(ch) {
Ok(()) => return, Err(_) => unreachable!("Stack capacity check failed in push"),
}
}
} else {
(*self.data.heap).push(ch);
}
}
}
#[inline(always)]
pub fn push_str(&mut self, s: &str) {
unsafe {
if self.on_stack {
let stack_str = &mut *self.data.stack;
if stack_str.len() + s.len() > N {
self.spill_to_heap_and_push_str(s);
} else {
match stack_str.push_str(s) {
Ok(()) => return, Err(_) => unreachable!("Stack capacity check failed in push str"),
}
}
} else {
(*self.data.heap).push_str(s);
}
}
}
#[inline(always)]
pub fn len(&self) -> usize {
unsafe {
if self.on_stack {
self.data.stack.len()
} else {
self.data.heap.len()
}
}
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline(always)]
pub fn clear(&mut self) {
unsafe {
if self.on_stack {
(*self.data.stack).clear();
} else {
(*self.data.heap).clear();
}
}
}
#[inline(never)]
unsafe fn spill_to_heap_and_push_str(&mut self, pending_str: &str) {
unsafe {
let stack_bytes = self.data.stack.as_bytes();
let pending_bytes = pending_str.as_bytes();
let total_len = stack_bytes.len() + pending_bytes.len();
let cap = std::cmp::max(total_len, N * 2);
let mut heap_vec = Vec::with_capacity(cap);
heap_vec.extend_from_slice(stack_bytes);
heap_vec.extend_from_slice(pending_bytes);
let new_heap = String::from_utf8_unchecked(heap_vec);
ManuallyDrop::drop(&mut self.data.stack);
self.data.heap = ManuallyDrop::new(new_heap);
self.on_stack = false;
}
}
#[inline(never)]
unsafe fn spill_to_heap_and_push_char(&mut self, pending_char: char) {
unsafe {
let stack_bytes = self.data.stack.as_bytes();
let cap = std::cmp::max(stack_bytes.len() + 4, N * 2);
let mut heap_vec = Vec::with_capacity(cap);
heap_vec.extend_from_slice(stack_bytes);
let char_len = pending_char.len_utf8();
let old_len = heap_vec.len();
heap_vec.set_len(old_len + char_len);
pending_char.encode_utf8(&mut heap_vec[old_len..]);
let new_heap = String::from_utf8_unchecked(heap_vec);
ManuallyDrop::drop(&mut self.data.stack);
self.data.heap = ManuallyDrop::new(new_heap);
self.on_stack = false;
}
}
}
impl<const N: usize> Deref for SmallString<N> {
type Target = str;
fn deref(&self) -> &Self::Target {
unsafe {
if self.on_stack {
self.data.stack.as_str()
} else {
self.data.heap.as_str()
}
}
}
}
impl<const N: usize> DerefMut for SmallString<N> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe {
if self.on_stack {
(*self.data.stack).as_mut_str()
} else {
(*self.data.heap).as_mut_str()
}
}
}
}
impl<const N: usize> SmallString<N> {
#[inline(always)]
pub fn as_str(&self) -> &str {
&**self
}
#[inline(always)]
pub fn as_mut_str(&mut self) -> &mut str {
&mut **self
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
#[inline(always)]
pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
unsafe { self.as_mut_str().as_bytes_mut() }
}
}
impl<const N: usize> Clone for SmallString<N> {
fn clone(&self) -> Self {
unsafe {
if self.on_stack {
let stack_clone = (*self.data.stack).clone();
SmallString {
on_stack: true,
data: StringData {
stack: ManuallyDrop::new(stack_clone),
},
}
} else {
let heap_clone = (*self.data.heap).clone();
SmallString {
on_stack: false,
data: StringData {
heap: ManuallyDrop::new(heap_clone),
},
}
}
}
}
}
impl<const N: usize> Drop for SmallString<N> {
fn drop(&mut self) {
unsafe {
if self.on_stack {
ManuallyDrop::drop(&mut self.data.stack);
} else {
ManuallyDrop::drop(&mut self.data.heap);
}
}
}
}
impl<const N: usize> Default for SmallString<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> fmt::Display for SmallString<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f) }
}
impl<const N: usize> fmt::Debug for SmallString<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
impl<const N: usize> From<&str> for SmallString<N> {
fn from(s: &str) -> Self {
Self::from_str(s)
}
}
impl<const N: usize> fmt::Write for SmallString<N> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.push_str(s);
Ok(())
}
}
impl<const N: usize, const M: usize> PartialEq<SmallString<M>> for SmallString<N> {
fn eq(&self, other: &SmallString<M>) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize> Eq for SmallString<N> {}
impl<const N: usize> PartialEq<str> for SmallString<N> {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl<'a, const N: usize> PartialEq<&'a str> for SmallString<N> {
fn eq(&self, other: &&'a str) -> bool {
self.as_str() == *other
}
}
impl<const N: usize> PartialEq<SmallString<N>> for &str {
fn eq(&self, other: &SmallString<N>) -> bool {
*self == other.as_str()
}
}
impl<const N: usize> PartialEq<String> for SmallString<N> {
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize, const M: usize> PartialOrd<SmallString<M>> for SmallString<N> {
fn partial_cmp(&self, other: &SmallString<M>) -> Option<Ordering> {
Some(self.as_str().cmp(other.as_str()))
}
}
impl<const N: usize> Ord for SmallString<N> {
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl<const N: usize> Hash for SmallString<N> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl<const N: usize> Borrow<str> for SmallString<N> {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl<const N: usize> BorrowMut<str> for SmallString<N> {
fn borrow_mut(&mut self) -> &mut str {
self.as_mut_str()
}
}
impl<const N: usize> AsRef<str> for SmallString<N> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<const N: usize> AsRef<[u8]> for SmallString<N> {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<const N: usize> FromIterator<char> for SmallString<N> {
fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
let mut s = Self::new();
for c in iter {
s.push(c); }
s
}
}
impl<'a, const N: usize> FromIterator<&'a str> for SmallString<N> {
fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> Self {
let mut s = Self::new();
for str_slice in iter {
s.push_str(str_slice);
}
s
}
}
impl<const N: usize> Extend<char> for SmallString<N> {
fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
for c in iter {
self.push(c);
}
}
}
impl<'a, const N: usize> Extend<&'a str> for SmallString<N> {
fn extend<I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
for str_slice in iter {
self.push_str(str_slice);
}
}
}
impl<const N: usize> SmallString<N> {
#[inline(always)]
pub fn pop(&mut self) -> Option<char> {
unsafe {
if self.on_stack {
(*self.data.stack).pop()
} else {
(*self.data.heap).pop()
}
}
}
#[inline(always)]
pub fn truncate(&mut self, new_len: usize) {
unsafe {
if self.on_stack {
(*self.data.stack).truncate(new_len);
} else {
(*self.data.heap).truncate(new_len);
}
}
}
#[inline(always)]
pub fn capacity(&self) -> usize {
unsafe {
if self.on_stack {
N } else {
(*self.data.heap).capacity()
}
}
}
}
impl<const N: usize> SmallString<N> {
#[inline(always)]
pub fn reserve(&mut self, additional: usize) {
unsafe {
if self.on_stack {
let len = self.len();
if len + additional > N {
let stack_bytes = self.data.stack.as_bytes();
let cap = std::cmp::max(len + additional, N * 2);
let mut heap_vec = Vec::with_capacity(cap);
heap_vec.extend_from_slice(stack_bytes);
let new_heap = String::from_utf8_unchecked(heap_vec);
ManuallyDrop::drop(&mut self.data.stack);
self.data.heap = ManuallyDrop::new(new_heap);
self.on_stack = false;
}
} else {
(*self.data.heap).reserve(additional);
}
}
}
}
impl<const N: usize> SmallString<N> {
pub fn from_utf8(vec: Vec<u8>) -> Result<Self, std::string::FromUtf8Error> {
let s_std = String::from_utf8(vec)?;
if s_std.len() <= N {
let mut small = Self::new();
small.push_str(&s_std);
Ok(small)
} else {
Ok(Self {
on_stack: false,
data: StringData {
heap: ManuallyDrop::new(s_std),
},
})
}
}
pub fn from_utf8_lossy(bytes: &[u8]) -> Self {
let cow = String::from_utf8_lossy(bytes);
match cow {
std::borrow::Cow::Borrowed(s) => Self::from(s),
std::borrow::Cow::Owned(s) => {
if s.len() <= N {
Self::from(s.as_str())
} else {
Self {
on_stack: false,
data: StringData {
heap: ManuallyDrop::new(s),
},
}
}
}
}
}
}
impl<const N: usize> SmallString<N> {
pub fn into_string(self) -> String {
let mut this = ManuallyDrop::new(self);
unsafe {
if this.on_stack {
let stack_str = &*this.data.stack;
stack_str.as_str().to_string()
} else {
ManuallyDrop::take(&mut this.data.heap)
}
}
}
pub fn into_bytes(self) -> Vec<u8> {
self.into_string().into_bytes()
}
}
impl<const N: usize> SmallString<N> {
pub fn retain<F>(&mut self, mut f: F)
where
F: FnMut(char) -> bool,
{
unsafe {
if self.on_stack {
let stack_str = &mut *self.data.stack;
let mut temp: heapless::String<N> = heapless::String::new();
for c in stack_str.chars() {
if f(c) {
match temp.push(c) {
Ok(()) => continue,
Err(_) => unreachable!("temp string capacity check failed in push"),
}
}
}
*stack_str = temp;
} else {
(*self.data.heap).retain(f);
}
}
}
}
impl<const N: usize> SmallString<N> {
pub fn shrink_to_fit(&mut self) {
unsafe {
if !self.on_stack {
(*self.data.heap).shrink_to_fit();
}
}
}
}
#[cfg(test)]
mod string_basic_tests {
use super::*;
use std::borrow::Borrow;
use std::collections::HashSet;
use std::collections::hash_map::DefaultHasher;
use std::fmt::Write; use std::hash::{Hash, Hasher};
#[test]
fn test_string_traits_borrow() {
use std::borrow::{Borrow, BorrowMut};
let mut s: SmallString<16> = SmallString::from("abc");
let b: &str = s.borrow();
assert_eq!(b, "abc");
let b_mut: &mut str = s.borrow_mut();
b_mut.make_ascii_uppercase();
assert_eq!(s.as_str(), "ABC");
}
#[test]
fn test_string_stack_ops_basic() {
let mut s: SmallString<16> = SmallString::new();
assert!(s.is_on_stack());
assert!(s.is_empty());
s.push_str("Hello");
assert_eq!(s.len(), 5);
assert_eq!(s.as_str(), "Hello");
assert!(s.is_on_stack());
s.push(' ');
s.push_str("World");
assert_eq!(s.len(), 11);
assert_eq!(&*s, "Hello World"); assert!(s.is_on_stack());
}
#[test]
fn test_string_spill_trigger_on_exact_capacity() {
let mut s: SmallString<5> = SmallString::new();
s.push_str("12345");
assert_eq!(s.len(), 5);
assert!(s.is_on_stack(), "Should remain on stack at exact capacity");
s.push('6');
assert!(!s.is_on_stack(), "Should spill after N+1");
assert_eq!(s.as_str(), "123456");
}
#[test]
fn test_string_spill_trigger_on_push_str() {
let mut s: SmallString<4> = SmallString::new();
s.push_str("Hi");
s.push_str(" there");
assert!(!s.is_on_stack());
assert_eq!(s.as_str(), "Hi there");
assert_eq!(s.len(), 8);
}
#[test]
fn test_string_spill_trigger_on_push_char() {
let mut s: SmallString<3> = SmallString::new();
s.push('A');
s.push('B');
s.push('C');
s.push('D');
assert!(!s.is_on_stack());
assert_eq!(s.as_str(), "ABCD");
}
#[test]
fn test_string_spill_trigger_on_multibyte_char() {
let mut s: SmallString<6> = SmallString::new();
s.push('🦀'); assert!(s.is_on_stack());
assert_eq!(s.len(), 4);
s.push('🚀');
assert!(!s.is_on_stack());
assert_eq!(s.as_str(), "🦀🚀");
assert_eq!(s.len(), 8);
}
#[test]
fn test_string_spill_trigger_on_multibyte_str() {
let mut s: SmallString<5> = SmallString::new();
s.push_str("hi");
s.push_str("_👋");
assert!(!s.is_on_stack());
assert_eq!(s.as_str(), "hi_👋");
}
#[test]
fn test_string_traits_fmt_write() {
let mut s: SmallString<16> = SmallString::new();
write!(s, "Value: {}", 100).unwrap();
assert_eq!(s.as_str(), "Value: 100");
assert!(s.is_on_stack());
write!(s, " and a very long suffix to force spill").unwrap();
assert!(!s.is_on_stack());
assert!(s.contains("Value: 100"));
assert!(s.contains("suffix"));
}
#[test]
fn test_string_spill_trigger_on_large_growth() {
let mut s: SmallString<4> = SmallString::new();
s.push_str("12");
let huge_chunk = "a".repeat(100);
s.push_str(&huge_chunk);
assert!(!s.is_on_stack());
assert_eq!(s.len(), 102);
assert!(s.ends_with("aaaa"));
}
#[test]
fn test_string_any_storage_zero_capacity() {
let mut s: SmallString<0> = SmallString::new();
assert!(s.is_on_stack());
s.push('a');
assert!(!s.is_on_stack()); assert_eq!(s.as_str(), "a");
}
#[test]
fn test_string_any_storage_clear_reuse() {
let mut s: SmallString<4> = SmallString::new();
s.push_str("abc");
s.clear();
assert!(s.is_empty());
assert!(s.is_on_stack());
s.push_str("xyz");
assert_eq!(s.as_str(), "xyz");
s.push_str("12345"); assert!(!s.is_on_stack());
s.clear();
assert!(s.is_empty());
assert!(!s.is_on_stack());
s.push_str("HeapReuse");
assert_eq!(s.as_str(), "HeapReuse");
}
#[test]
fn test_string_traits_deref_methods() {
let s: SmallString<10> = SmallString::from("hello");
assert!(s.starts_with("he"));
assert!(s.contains("ll"));
assert_eq!(s.to_uppercase(), "HELLO"); assert_eq!(s.find('o'), Some(4));
}
#[test]
fn test_string_traits_clone_on_stack() {
let mut original: SmallString<16> = SmallString::new();
original.push_str("Hello");
assert!(original.is_on_stack());
let mut copy = original.clone();
assert!(copy.is_on_stack());
assert_eq!(original.as_str(), copy.as_str());
copy.push_str(" World");
assert_eq!(original.as_str(), "Hello");
assert_eq!(copy.as_str(), "Hello World");
}
#[test]
fn test_string_traits_clone_on_heap() {
let mut original: SmallString<4> = SmallString::new();
original.push_str("Hello");
assert!(!original.is_on_stack());
let mut copy = original.clone();
assert!(!copy.is_on_stack());
assert_eq!(original.as_str(), copy.as_str());
copy.push_str(" World");
assert_eq!(original.as_str(), "Hello");
assert_eq!(copy.as_str(), "Hello World");
}
fn calculate_hash<T: Hash>(t: &T) -> u64 {
let mut s = DefaultHasher::new();
t.hash(&mut s);
s.finish()
}
#[test]
fn test_string_traits_equality() {
let s1: SmallString<16> = SmallString::from("hello");
let s2: SmallString<16> = SmallString::from("hello");
let s3: SmallString<16> = SmallString::from("world");
assert_eq!(s1, s2);
assert_ne!(s1, s3);
let h1: SmallString<2> = SmallString::from("hello");
let h2: SmallString<2> = SmallString::from("hello");
assert_eq!(h1, h2);
assert!(!h1.is_on_stack());
assert_eq!(s1, h1);
assert_eq!(s1, "hello");
assert_eq!("hello", s1);
assert_eq!(s1, String::from("hello"));
}
#[test]
fn test_string_traits_ordering() {
let apple: SmallString<16> = SmallString::from("Apple");
let banana: SmallString<16> = SmallString::from("Banana");
assert!(apple < banana);
let mut list = vec![banana.clone(), apple.clone()];
list.sort();
assert_eq!(list[0], "Apple");
assert_eq!(list[1], "Banana");
}
#[test]
fn test_string_traits_hashing() {
let s_stack: SmallString<16> = SmallString::from("testing");
let s_heap: SmallString<2> = SmallString::from("testing"); let s_std: String = String::from("testing");
let s_str: &str = "testing";
let h1 = calculate_hash(&s_stack);
let h2 = calculate_hash(&s_heap);
let h3 = calculate_hash(&s_std);
let h4 = calculate_hash(&s_str);
assert_eq!(h1, h2, "Stack and Heap hashing differ!");
assert_eq!(h1, h3, "SmallString hash differs from String hash!");
assert_eq!(h1, h4, "SmallString hash differs from &str hash!");
let mut set = HashSet::new();
set.insert(s_stack.clone());
assert!(set.contains("testing")); assert!(set.contains(s_heap.as_str())); }
#[test]
fn test_string_traits_borrow_as_ref() {
let s: SmallString<16> = SmallString::from("hello");
fn takes_str(_: &str) {}
takes_str(s.as_ref());
takes_str(s.borrow());
fn takes_as_ref<T: AsRef<str>>(_: T) {}
takes_as_ref(s); }
#[test]
fn test_string_traits_from_iterator() {
let chars = vec!['a', 'b', 'c'];
let s_stack: SmallString<16> = chars.into_iter().collect();
assert_eq!(s_stack, "abc");
assert!(s_stack.is_on_stack());
let many_chars = vec!['a'; 100];
let s_heap: SmallString<16> = many_chars.into_iter().collect();
assert_eq!(s_heap.len(), 100);
assert!(!s_heap.is_on_stack());
let strings = vec!["Hello", " ", "World"];
let s_str: SmallString<32> = strings.into_iter().collect();
assert_eq!(s_str, "Hello World");
}
#[test]
fn test_string_traits_extend() {
let mut s: SmallString<4> = SmallString::new();
s.extend(vec!['H', 'i']);
assert_eq!(s, "Hi");
assert!(s.is_on_stack());
s.extend(vec!['!'; 20]);
assert!(!s.is_on_stack());
assert_eq!(s.len(), 22);
}
#[test]
fn test_string_any_storage_pop() {
let mut s: SmallString<16> = SmallString::from("abc");
assert_eq!(s.pop(), Some('c'));
assert_eq!(s.pop(), Some('b'));
assert_eq!(s, "a");
let mut h: SmallString<2> = SmallString::from("abc"); assert!(!h.is_on_stack());
assert_eq!(h.pop(), Some('c'));
assert_eq!(h, "ab");
let mut empty: SmallString<4> = SmallString::new();
assert_eq!(empty.pop(), None);
}
#[test]
fn test_string_any_storage_truncate_v2() {
let mut s: SmallString<16> = SmallString::from("Hello World");
s.truncate(5);
assert_eq!(s, "Hello");
assert_eq!(s.len(), 5);
s.truncate(100);
assert_eq!(s, "Hello");
let mut h: SmallString<2> = SmallString::from("Hello World"); assert!(!h.is_on_stack());
h.truncate(2);
assert_eq!(h, "He");
}
#[test]
#[should_panic]
fn test_string_any_storage_truncate_panic() {
let mut s: SmallString<16> = SmallString::from("🦀"); s.truncate(2);
}
#[test]
fn test_string_any_storage_reserve() {
let mut s: SmallString<16> = SmallString::new();
s.push_str("Hi");
s.reserve(4); assert!(s.is_on_stack());
s.reserve(20);
assert!(!s.is_on_stack());
assert_eq!(s.as_str(), "Hi");
assert!(s.capacity() >= 22);
s.reserve(100);
assert!(s.capacity() >= 102);
}
#[test]
fn test_string_any_storage_into_string() {
let s_stack: SmallString<16> = SmallString::from("stack");
let std_str = s_stack.into_string();
assert_eq!(std_str, "stack");
let _: String = std_str;
let s_heap: SmallString<2> = SmallString::from("heap");
let std_str2 = s_heap.into_string();
assert_eq!(std_str2, "heap");
}
#[test]
fn test_string_any_storage_from_utf8() {
let bytes = vec![104, 101, 108, 108, 111];
let s: SmallString<16> = SmallString::from_utf8(bytes.clone()).unwrap();
assert!(s.is_on_stack());
assert_eq!(s, "hello");
let s2: SmallString<2> = SmallString::from_utf8(bytes).unwrap();
assert!(!s2.is_on_stack());
assert_eq!(s2, "hello");
let invalid = vec![0, 159, 146, 150];
assert!(SmallString::<16>::from_utf8(invalid).is_err());
}
#[test]
fn test_string_any_storage_retain() {
let mut s: SmallString<16> = SmallString::from("AbCdEf");
s.retain(|c| c.is_lowercase());
assert_eq!(s, "bdf");
assert!(s.is_on_stack());
let mut h: SmallString<2> = SmallString::from("AbCdEf"); h.retain(|c| c.is_uppercase());
assert_eq!(h, "ACE");
assert!(!h.is_on_stack());
}
#[test]
fn test_string_any_storage_into_bytes() {
let s: SmallString<16> = SmallString::from("ABC");
let bytes = s.into_bytes();
assert_eq!(bytes, vec![65, 66, 67]);
}
#[test]
fn test_string_any_storage_from_utf8_lossy() {
let bytes = b"hello \xF0\x90\x80world"; let s: SmallString<16> = SmallString::from_utf8_lossy(bytes);
assert!(s.contains("hello "));
assert!(s.contains("world"));
assert!(s.is_on_stack());
let huge_invalid = b"a".repeat(100);
let s2: SmallString<16> = SmallString::from_utf8_lossy(&huge_invalid);
assert!(!s2.is_on_stack());
}
#[test]
fn test_string_any_storage_as_bytes_mut() {
let mut s: SmallString<16> = SmallString::from("abc");
unsafe {
let bytes = s.as_bytes_mut();
bytes[0] = b'z';
}
assert_eq!(s, "zbc");
}
#[test]
fn test_string_traits_debug_display() {
let h: SmallString<2> = SmallString::from("a");
let debug = format!("{:?}", h);
assert_eq!(debug, "\"a\"");
let display = format!("{}", h);
assert_eq!(display, "a");
}
#[test]
fn test_string_any_storage_truncate() {
let mut s: SmallString<16> = SmallString::from("hello");
s.truncate(2);
assert_eq!(s, "he");
let mut h: SmallString<2> = SmallString::from("abc");
h.truncate(1);
assert_eq!(h, "a");
}
#[test]
fn test_string_any_storage_deref_mut() {
let mut s_mut = SmallString::<16>::from("abc");
s_mut.as_mut_str().make_ascii_uppercase();
assert_eq!(s_mut, "ABC");
let mut h: SmallString<1> = SmallString::from_str("abc");
h.as_mut_str().make_ascii_uppercase();
assert_eq!(h, "ABC");
}
#[test]
fn test_string_any_storage_partial_eq_variants() {
let h: SmallString<1> = SmallString::from_str("abc");
assert!(h == "abc");
let s_ref = "abc";
assert!(h == s_ref);
}
#[test]
fn test_string_any_storage_extend_char() {
let mut h: SmallString<1> = SmallString::from_str("abc");
h.extend(['!', '?'].iter().cloned());
assert_eq!(h, "abc!?");
}
#[test]
fn test_string_any_storage_from_utf8_long() {
let long_bytes = b"a".repeat(100);
let s_long = SmallString::<16>::from_utf8(long_bytes).unwrap();
assert!(!s_long.is_on_stack());
}
}
#[cfg(test)]
mod string_coverage_tests {
use super::*;
#[test]
fn test_any_string_trait_std_string_implementation() {
let mut s = String::from("abc");
let any: &mut dyn AnyString = &mut s;
assert_eq!(any.as_str(), "abc");
assert_eq!(any.len(), 3);
assert!(!any.is_empty());
any.push('d');
any.push_str("ef");
assert_eq!(any.as_str(), "abcdef");
assert_eq!(any.pop(), Some('f'));
any.truncate(3);
assert_eq!(any.as_str(), "abc");
any.clear();
assert!(any.is_empty());
}
#[test]
fn test_any_string_trait_small_string_implementation() {
let mut s: SmallString<4> = SmallString::new();
let any: &mut dyn AnyString = &mut s;
any.push_str("abc");
assert_eq!(any.len(), 3);
any.push('d');
assert_eq!(any.as_str(), "abcd");
any.pop();
any.truncate(1);
any.clear();
assert!(any.is_empty());
}
#[test]
fn test_small_string_heap_storage_utf8_validity_and_retain() {
let mut s: SmallString<2> = SmallString::new();
s.push_str("abc"); assert!(!s.is_on_stack());
assert!(s.capacity() >= 2);
let long_vec = vec![b'a'; 10];
let s2 = SmallString::<2>::from_utf8(long_vec).unwrap();
assert!(!s2.is_on_stack());
let s3 = SmallString::<2>::from_utf8_lossy(&[b'a'; 10]);
assert!(!s3.is_on_stack());
let mut s4: SmallString<2> = SmallString::from("abcde");
s4.retain(|c| c != 'b');
assert_eq!(s4.as_str(), "acde");
s4.shrink_to_fit();
}
#[test]
fn test_small_string_formatting_comparison_and_extension() {
let mut s: SmallString<4> = SmallString::from("abc");
assert_eq!(format!("{}", s), "abc");
assert_eq!(format!("{:?}", s), "\"abc\"");
let s2 = SmallString::<8>::from("abc");
assert_eq!(s, s2);
assert_eq!(s, "abc");
assert_eq!("abc", s);
assert_eq!(s, String::from("abc"));
let s3 = SmallString::<4>::from("abd");
assert!(s < s3);
assert_eq!(s.cmp(&s3), std::cmp::Ordering::Less);
let mut h = std::collections::hash_map::DefaultHasher::new();
s.hash(&mut h);
let b: &str = s.borrow();
assert_eq!(b, "abc");
let b_mut: &mut str = s.borrow_mut();
b_mut.make_ascii_uppercase();
assert_eq!(s.as_str(), "ABC");
let r: &str = s.as_ref();
assert_eq!(r, "ABC");
let r_bytes: &[u8] = s.as_ref();
assert_eq!(r_bytes, b"ABC");
let mut s4: SmallString<4> = SmallString::new();
s4.extend(['a', 'b']);
s4.extend(["cd", "ef"]); assert_eq!(s4.as_str(), "abcdef");
let s5 = SmallString::<4>::from_iter(['x', 'y']);
assert_eq!(s5.as_str(), "xy");
let s6 = SmallString::<4>::from_iter(["hi", "ho"]);
assert_eq!(s6.as_str(), "hiho");
}
#[test]
fn test_small_string_fmt_write_trait() {
use std::fmt::Write;
let mut s: SmallString<4> = SmallString::new();
write!(s, "{}", 12345).unwrap();
assert_eq!(s.as_str(), "12345");
}
}