1use std::{
26 borrow::{Borrow, Cow},
27 fmt,
28 hash::{Hash, Hasher},
29 ops::Deref,
30 str::from_utf8,
31};
32
33pub(crate) const MAX_INLINE_STR_LEN: usize = 3 * std::mem::size_of::<isize>() - 2;
34
35#[derive(Debug)]
38pub struct StringTooLongError;
39
40#[derive(Debug, Clone, Copy, Eq)]
43pub struct InlineStr {
44 inner: [u8; MAX_INLINE_STR_LEN],
45 len: u8,
46}
47
48impl AsRef<str> for InlineStr {
49 fn as_ref(&self) -> &str {
50 self.deref()
51 }
52}
53
54impl Hash for InlineStr {
55 fn hash<H: Hasher>(&self, state: &mut H) {
56 self.deref().hash(state);
57 }
58}
59
60impl From<char> for InlineStr {
61 fn from(c: char) -> Self {
62 let mut inner = [0u8; MAX_INLINE_STR_LEN];
63 c.encode_utf8(&mut inner);
64 let len = c.len_utf8() as u8;
65 Self { inner, len }
66 }
67}
68
69impl std::cmp::PartialEq<InlineStr> for InlineStr {
70 fn eq(&self, other: &InlineStr) -> bool {
71 self.deref() == other.deref()
72 }
73}
74
75impl TryFrom<&str> for InlineStr {
76 type Error = StringTooLongError;
77
78 fn try_from(s: &str) -> Result<InlineStr, StringTooLongError> {
79 let len = s.len();
80 if len <= MAX_INLINE_STR_LEN {
81 let mut inner = [0u8; MAX_INLINE_STR_LEN];
82
83 debug_assert!(
84 len <= MAX_INLINE_STR_LEN,
85 "InlineStr: len {} exceeds MAX_INLINE_STR_LEN {}",
86 len,
87 MAX_INLINE_STR_LEN
88 );
89
90 if let Some(dest) = inner.get_mut(..len) {
91 dest.copy_from_slice(s.as_bytes());
92 }
93
94 let len = len as u8;
95 Ok(Self { inner, len })
96 } else {
97 Err(StringTooLongError)
98 }
99 }
100}
101
102impl Deref for InlineStr {
103 type Target = str;
104
105 fn deref(&self) -> &str {
106 let len = self.len as usize;
107 self.inner
108 .get(..len)
109 .and_then(|bytes| from_utf8(bytes).ok())
110 .unwrap_or_default()
111 }
112}
113
114impl fmt::Display for InlineStr {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "{}", self.as_ref())
117 }
118}
119
120#[derive(Eq)]
141pub enum CowStr<'a> {
142 Boxed(Box<str>),
144
145 Borrowed(&'a str),
147
148 Inlined(InlineStr),
150}
151
152impl AsRef<str> for CowStr<'_> {
153 fn as_ref(&self) -> &str {
154 self.deref()
155 }
156}
157
158impl Hash for CowStr<'_> {
159 fn hash<H: Hasher>(&self, state: &mut H) {
160 self.deref().hash(state);
161 }
162}
163
164impl std::clone::Clone for CowStr<'_> {
165 fn clone(&self) -> Self {
166 match self {
167 CowStr::Boxed(s) => match InlineStr::try_from(&**s) {
168 Ok(inline) => CowStr::Inlined(inline),
169 Err(..) => CowStr::Boxed(s.clone()),
170 },
171 CowStr::Borrowed(s) => CowStr::Borrowed(s),
172 CowStr::Inlined(s) => CowStr::Inlined(*s),
173 }
174 }
175}
176
177impl<'a> std::cmp::PartialEq<CowStr<'a>> for CowStr<'a> {
178 fn eq(&self, other: &CowStr<'_>) -> bool {
179 self.deref() == other.deref()
180 }
181}
182
183impl<'a> From<&'a str> for CowStr<'a> {
184 fn from(s: &'a str) -> Self {
185 CowStr::Borrowed(s)
186 }
187}
188
189impl From<String> for CowStr<'_> {
190 fn from(s: String) -> Self {
191 CowStr::Boxed(s.into_boxed_str())
192 }
193}
194
195impl From<char> for CowStr<'_> {
196 fn from(c: char) -> Self {
197 CowStr::Inlined(c.into())
198 }
199}
200
201impl<'a> From<Cow<'a, str>> for CowStr<'a> {
202 fn from(s: Cow<'a, str>) -> Self {
203 match s {
204 Cow::Borrowed(s) => CowStr::Borrowed(s),
205 Cow::Owned(s) => CowStr::Boxed(s.into_boxed_str()),
206 }
207 }
208}
209
210impl<'a> From<CowStr<'a>> for Cow<'a, str> {
211 fn from(s: CowStr<'a>) -> Self {
212 match s {
213 CowStr::Boxed(s) => Cow::Owned(s.to_string()),
214 CowStr::Inlined(s) => Cow::Owned(s.to_string()),
215 CowStr::Borrowed(s) => Cow::Borrowed(s),
216 }
217 }
218}
219
220impl<'a> From<Cow<'a, char>> for CowStr<'a> {
221 fn from(s: Cow<'a, char>) -> Self {
222 CowStr::Inlined(InlineStr::from(*s))
223 }
224}
225
226impl Deref for CowStr<'_> {
227 type Target = str;
228
229 fn deref(&self) -> &str {
230 match self {
231 CowStr::Boxed(b) => b,
232 CowStr::Borrowed(b) => b,
233 CowStr::Inlined(s) => s.deref(),
234 }
235 }
236}
237
238impl Borrow<str> for CowStr<'_> {
239 fn borrow(&self) -> &str {
240 self.deref()
241 }
242}
243
244impl CowStr<'_> {
245 pub fn into_string(self) -> String {
247 match self {
248 CowStr::Boxed(b) => b.into(),
249 CowStr::Borrowed(b) => b.to_owned(),
250 CowStr::Inlined(s) => s.deref().to_owned(),
251 }
252 }
253}
254
255impl fmt::Display for CowStr<'_> {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write!(f, "{}", self.as_ref())
258 }
259}
260
261impl fmt::Debug for CowStr<'_> {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 if f.alternate() {
264 write!(f, "{:?}", self.as_ref())
265 } else {
266 match self {
267 Self::Boxed(b) => f.debug_tuple("CowStr::Boxed").field(b).finish(),
268 Self::Borrowed(b) => f.debug_tuple("CowStr::Borrowed").field(b).finish(),
269 Self::Inlined(s) => f.debug_tuple("CowStr::Inlined").field(s).finish(),
270 }
271 }
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 #![allow(clippy::panic)]
278 #![allow(clippy::unwrap_used)]
279
280 mod string_too_long_err {
281 use crate::strings::StringTooLongError;
282
283 #[test]
284 fn impl_debug() {
285 let e = StringTooLongError;
286 assert_eq!(format!("{e:#?}"), "StringTooLongError");
287 }
288 }
289
290 mod inline_str {
291 use std::ops::Deref;
292
293 use crate::strings::*;
294
295 #[test]
296 fn from_ascii() {
297 let s: InlineStr = 'a'.into();
298 assert_eq!("a", s.deref());
299 }
300
301 #[test]
302 fn from_unicode() {
303 let s: InlineStr = 'π'.into();
304 assert_eq!("π", s.deref());
305 }
306
307 #[test]
308 fn impl_debug() {
309 let s: InlineStr = 'a'.into();
310 assert_eq!(
311 format!("{s:#?}"),
312 r#"InlineStr {
313 inner: [
314 97,
315 0,
316 0,
317 0,
318 0,
319 0,
320 0,
321 0,
322 0,
323 0,
324 0,
325 0,
326 0,
327 0,
328 0,
329 0,
330 0,
331 0,
332 0,
333 0,
334 0,
335 0,
336 ],
337 len: 1,
338}"#
339 );
340 }
341
342 #[test]
343 fn impl_hash() {
344 use std::{
345 collections::hash_map::DefaultHasher,
346 hash::{Hash, Hasher},
347 };
348
349 let mut hasher = DefaultHasher::new();
350 "π".hash(&mut hasher);
351 let expected = hasher.finish();
352
353 let s: InlineStr = 'π'.into();
354 let mut hasher = DefaultHasher::new();
355 s.hash(&mut hasher);
356 let actual = hasher.finish();
357
358 let s: InlineStr = 'a'.into();
359 let mut hasher = DefaultHasher::new();
360 s.hash(&mut hasher);
361 let mismatch = hasher.finish();
362
363 assert_eq!(expected, actual);
364 assert_ne!(expected, mismatch);
365 }
366
367 #[test]
368 fn impl_partial_eq() {
369 let s1: InlineStr = 'π'.into();
370 let s2: InlineStr = 'π'.into();
371 let s3: InlineStr = 'a'.into();
372
373 assert_eq!(s1, s2);
374 assert_ne!(s1, s3);
375 }
376
377 #[test]
378 #[allow(clippy::assertions_on_constants)]
379 fn max_len_atleast_four() {
380 assert!(MAX_INLINE_STR_LEN >= 4);
382 }
383
384 #[test]
385 #[cfg(target_pointer_width = "64")]
386 fn fits_twentytwo() {
387 let s = "0123456789abcdefghijkl";
388 let stack_str = InlineStr::try_from(s).unwrap();
389 assert_eq!(stack_str.deref(), s);
390 }
391
392 #[test]
393 #[cfg(target_pointer_width = "64")]
394 fn doesnt_fit_twentythree() {
395 let s = "0123456789abcdefghijklm";
396 let _stack_str = InlineStr::try_from(s).unwrap_err();
397 }
398 }
399
400 mod cow_str {
401 use std::{
402 borrow::{Borrow, Cow},
403 ops::Deref,
404 };
405
406 use crate::strings::*;
407
408 #[test]
409 fn size() {
410 let size = std::mem::size_of::<CowStr>();
411 let word_size = std::mem::size_of::<isize>();
412 assert_eq!(3 * word_size, size);
413 }
414
415 #[test]
416 fn char_to_string() {
417 let c = 'θ';
418 let smort: CowStr = c.into();
419 let owned: String = smort.to_string();
420 let expected = "θ".to_owned();
421 assert_eq!(expected, owned);
422 }
423
424 #[test]
425 #[cfg(target_pointer_width = "64")]
426 fn small_boxed_str_clones_to_stack() {
427 let s = "0123456789abcde".to_owned();
428 let smort: CowStr = s.into();
429 let smort_clone = smort.clone();
430
431 if let CowStr::Inlined(..) = smort_clone {
432 } else {
433 panic!("Expected a Inlined variant!");
434 }
435 }
436
437 #[test]
438 fn cow_to_cow_str() {
439 let s = "some text";
440 let cow = Cow::Borrowed(s);
441 let actual = CowStr::from(cow);
442 let expected = CowStr::Borrowed(s);
443 assert_eq!(actual, expected);
444 assert!(variant_eq(&actual, &expected));
445
446 let s = "some text".to_string();
447 let cow: Cow<str> = Cow::Owned(s.clone());
448 let actual = CowStr::from(cow);
449 let expected = CowStr::Boxed(s.into_boxed_str());
450 assert_eq!(actual, expected);
451 assert!(variant_eq(&actual, &expected));
452 }
453
454 #[test]
455 fn cow_str_to_cow() {
456 let s = "some text";
457 let cow_str = CowStr::Borrowed(s);
458 let actual = Cow::from(cow_str);
459 let expected = Cow::Borrowed(s);
460 assert_eq!(actual, expected);
461 assert!(variant_eq(&actual, &expected));
462
463 let s = "s";
464 let inline_str: InlineStr = InlineStr::try_from(s).unwrap();
465 let cow_str = CowStr::Inlined(inline_str);
466 let actual = Cow::from(cow_str);
467 let expected: Cow<str> = Cow::Owned(s.to_string());
468 assert_eq!(actual, expected);
469 assert!(variant_eq(&actual, &expected));
470
471 let s = "s";
472 let cow_str = CowStr::Boxed(s.to_string().into_boxed_str());
473 let actual = Cow::from(cow_str);
474 let expected: Cow<str> = Cow::Owned(s.to_string());
475 assert_eq!(actual, expected);
476 assert!(variant_eq(&actual, &expected));
477 }
478
479 #[test]
480 fn cow_char_to_cow_str() {
481 let c = 'c';
482 let cow: Cow<char> = Cow::Owned(c);
483 let actual = CowStr::from(cow);
484 let expected = CowStr::Inlined(InlineStr::from(c));
485 assert_eq!(actual, expected);
486 assert!(variant_eq(&actual, &expected));
487
488 let c = 'c';
489 let cow: Cow<char> = Cow::Borrowed(&c);
490 let actual = CowStr::from(cow);
491 let expected = CowStr::Inlined(InlineStr::from(c));
492 assert_eq!(actual, expected);
493 assert!(variant_eq(&actual, &expected));
494 }
495
496 fn variant_eq<T>(a: &T, b: &T) -> bool {
497 std::mem::discriminant(a) == std::mem::discriminant(b)
498 }
499
500 #[test]
501 fn impl_debug_pretty_print_for_inline() {
502 let c = 'θ';
503 let s: CowStr = c.into();
504
505 assert_eq!(format!("{s:#?}"), r#""θ""#);
506 }
507
508 #[test]
509 fn impl_debug_pretty_print_for_boxed() {
510 let s = "blah blah blah".to_string();
511 let s: CowStr = s.into();
512
513 assert_eq!(format!("{s:#?}"), r#""blah blah blah""#);
514 }
515
516 #[test]
517 fn impl_debug_pretty_print_for_borrowed() {
518 let s: &'static str = "0123456789abcdefghijklm";
519 let s: CowStr = s.into();
520
521 assert_eq!(format!("{s:#?}"), r#""0123456789abcdefghijklm""#);
522 }
523
524 #[test]
525 fn impl_debug_for_inline() {
526 let c = 'θ';
527 let s: CowStr = c.into();
528
529 assert_eq!(
530 format!("{s:?}"),
531 "CowStr::Inlined(InlineStr { inner: [232, 151, 143, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 3 })"
532 );
533 }
534
535 #[test]
536 fn impl_debug_for_boxed() {
537 let s = "blah blah blah".to_string();
538 let s: CowStr = s.into();
539
540 assert_eq!(format!("{s:?}"), "CowStr::Boxed(\"blah blah blah\")");
541 }
542
543 #[test]
544 fn impl_debug_for_borrowed() {
545 let s: &'static str = "0123456789abcdefghijklm";
546 let s: CowStr = s.into();
547
548 assert_eq!(
549 format!("{s:?}"),
550 "CowStr::Borrowed(\"0123456789abcdefghijklm\")"
551 );
552 }
553
554 #[test]
555 fn impl_clone_boxed_long() {
556 let s = "this string won't fit in a box".to_owned();
557 let s: CowStr = s.into();
558 if let CowStr::Boxed(_) = s {
559 } else {
560 panic!("Expected Boxed case");
561 }
562
563 let s2 = s.clone();
564 assert_eq!(s.deref(), s2.deref());
565
566 if let CowStr::Boxed(_) = s2 {
567 } else {
568 panic!("Expected Boxed clone");
569 }
570 }
571
572 #[test]
573 fn impl_clone_borrowed() {
574 let s = "this long string is borrowed";
575 let s: CowStr = s.into();
576 if let CowStr::Borrowed(_) = s {
577 } else {
578 panic!("Expected Borrowed case");
579 }
580
581 let s2 = s.clone();
582 assert_eq!(s.deref(), s2.deref());
583
584 if let CowStr::Borrowed(_) = s2 {
585 } else {
586 panic!("Expected Borrowed clone");
587 }
588 }
589
590 #[test]
591 fn impl_clone_inlined() {
592 let s: CowStr = 's'.into();
593 if let CowStr::Inlined(_) = s {
594 } else {
595 panic!("Expected Inlined case");
596 }
597
598 let s2 = s.clone();
599 assert_eq!(s.deref(), s2.deref());
600
601 if let CowStr::Inlined(_) = s2 {
602 } else {
603 panic!("Expected Inlined clone");
604 }
605 }
606
607 #[test]
608 fn impl_hash() {
609 use std::{
610 collections::hash_map::DefaultHasher,
611 hash::{Hash, Hasher},
612 };
613
614 let mut hasher = DefaultHasher::new();
615 "π".hash(&mut hasher);
616 let expected = hasher.finish();
617
618 let s: CowStr = 'π'.into();
619 if let CowStr::Inlined(_) = s {
620 } else {
621 panic!("Expected Inlined case");
622 }
623 let mut hasher = DefaultHasher::new();
624 s.hash(&mut hasher);
625 let actual = hasher.finish();
626 assert_eq!(expected, actual);
627
628 let s = CowStr::Borrowed("π");
629 let mut hasher = DefaultHasher::new();
630 s.hash(&mut hasher);
631 let actual = hasher.finish();
632 assert_eq!(expected, actual);
633
634 let s = "π".to_owned();
635 let s: CowStr = s.into();
636 if let CowStr::Boxed(_) = s {
637 } else {
638 panic!("Expected Boxed case");
639 }
640 let mut hasher = DefaultHasher::new();
641 s.hash(&mut hasher);
642 assert_eq!(expected, actual);
643 }
644
645 #[test]
646 fn impl_from_str() {
647 let s = "xyz";
648 let s: CowStr = s.into();
649 assert_eq!(s.deref(), "xyz");
650
651 if let CowStr::Borrowed(_) = s {
652 } else {
653 panic!("Expected Borrowed case");
654 }
655 }
656
657 #[test]
658 fn impl_borrow() {
659 let s: CowStr = "xyz".into();
660 let s: &str = s.borrow();
661 assert_eq!(s, "xyz");
662 }
663
664 #[test]
665 fn into_string_boxed() {
666 let s = "this string won't fit in a box".to_owned();
667 let s: CowStr = s.into();
668 if let CowStr::Boxed(_) = s {
669 } else {
670 panic!("Expected Boxed case");
671 }
672
673 let s2 = s.into_string();
674 assert_eq!(&s2, "this string won't fit in a box");
675 }
676
677 #[test]
678 fn into_string_borrowed() {
679 let s = "this long string is borrowed";
680 let s: CowStr = s.into();
681 if let CowStr::Borrowed(_) = s {
682 } else {
683 panic!("Expected Borrowed case");
684 }
685
686 let s2 = s.into_string();
687 assert_eq!(&s2, "this long string is borrowed");
688 }
689
690 #[test]
691 fn into_string_inlined() {
692 let s: CowStr = 's'.into();
693 if let CowStr::Inlined(_) = s {
694 } else {
695 panic!("Expected Inlined case");
696 }
697
698 let s2 = s.into_string();
699 assert_eq!(&s2, "s");
700 }
701 }
702}