waterui_str/lib.rs
1#![doc = include_str!("../README.md")]
2extern crate alloc;
3
4mod impls;
5mod shared;
6use alloc::{
7 borrow::Cow,
8 boxed::Box,
9 string::{FromUtf8Error, String, ToString},
10 vec::Vec,
11};
12use shared::Shared;
13
14use core::{
15 borrow::Borrow,
16 mem::{ManuallyDrop, take},
17 ops::Deref,
18 ptr::NonNull,
19 slice,
20};
21
22nami_core::impl_constant!(Str);
23
24/// A string type that can be either a static reference or a ref-counted owned string.
25///
26/// `Str` combines the benefits of both `&'static str` and `String` with efficient
27/// cloning and passing, automatically using the most appropriate representation
28/// based on the source.
29pub struct Str {
30 /// Pointer to the string data.
31 ptr: NonNull<()>,
32
33 /// Length of the string in bytes.
34 /// If len >= 0, it points to a static string.
35 /// otherwise, it points to a Shared structure containing a reference-counted String,
36 len: isize,
37}
38
39impl Drop for Str {
40 /// Decrements the reference count for owned strings and frees the memory
41 /// when the reference count reaches zero.
42 ///
43 /// For static strings, this is a no-op.
44 fn drop(&mut self) {
45 if let Ok(shared) = self.as_shared() {
46 // SAFETY: `as_shared` returning `Ok` proves `len < 0`, so `ptr` is the
47 // leaked `Shared` box this `Str` holds a count on. `Drop` runs once, so the
48 // count is released once and the box is reclaimed only by the last owner.
49 unsafe {
50 if shared.is_unique() {
51 let ptr = self.ptr.cast::<Shared>().as_ptr();
52 let _ = Box::from_raw(ptr);
53 } else {
54 shared.decrement_count();
55 }
56 }
57 }
58 }
59}
60
61impl Clone for Str {
62 /// Creates a clone of the string.
63 ///
64 /// For static strings, this is a simple pointer copy.
65 /// For owned strings, this increments the reference count.
66 fn clone(&self) -> Self {
67 if let Ok(shared) = self.as_shared() {
68 // SAFETY: `as_shared` returning `Ok` proves this is a shared string, so
69 // there is a live `Shared` to count; the clone below takes that new count.
70 unsafe {
71 shared.increment_count();
72 }
73 }
74
75 Self {
76 ptr: self.ptr,
77 len: self.len,
78 }
79 }
80}
81
82impl Deref for Str {
83 type Target = str;
84
85 /// Provides access to the underlying string slice.
86 fn deref(&self) -> &Self::Target {
87 self.as_str()
88 }
89}
90
91impl Borrow<str> for Str {
92 /// Allows borrowing a `Str` as a string slice.
93 fn borrow(&self) -> &str {
94 self.as_str()
95 }
96}
97
98impl AsRef<str> for Str {
99 /// Converts `Str` to a string slice reference.
100 fn as_ref(&self) -> &str {
101 self.as_str()
102 }
103}
104
105impl AsRef<[u8]> for Str {
106 /// Converts `Str` to a byte slice reference.
107 fn as_ref(&self) -> &[u8] {
108 self.as_bytes()
109 }
110}
111
112impl Default for Str {
113 /// Creates a new empty `Str`.
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl From<Cow<'static, str>> for Str {
120 /// Creates a `Str` from a `Cow<'static, str>`.
121 ///
122 /// This will borrow from static strings and own dynamic strings.
123 fn from(value: Cow<'static, str>) -> Self {
124 match value {
125 Cow::Borrowed(s) => s.into(),
126 Cow::Owned(s) => s.into(),
127 }
128 }
129}
130
131/// Implementations available when the `std` library is available.
132mod std_on {
133 use alloc::{string::FromUtf8Error, vec::IntoIter};
134
135 use crate::Str;
136
137 extern crate std;
138
139 use core::{net::SocketAddr, ops::Deref};
140 use std::{
141 ffi::{OsStr, OsString},
142 io,
143 net::ToSocketAddrs,
144 path::Path,
145 };
146
147 impl AsRef<OsStr> for Str {
148 /// Converts `Str` to an OS string slice reference.
149 fn as_ref(&self) -> &OsStr {
150 self.deref().as_ref()
151 }
152 }
153
154 impl AsRef<Path> for Str {
155 /// Converts `Str` to a path reference.
156 fn as_ref(&self) -> &Path {
157 self.deref().as_ref()
158 }
159 }
160
161 impl TryFrom<OsString> for Str {
162 type Error = FromUtf8Error;
163
164 /// Attempts to create a `Str` from an `OsString`.
165 ///
166 /// This will fail if the `OsString` contains invalid UTF-8 data.
167 fn try_from(value: OsString) -> Result<Self, Self::Error> {
168 Self::from_utf8(value.into_encoded_bytes())
169 }
170 }
171
172 impl ToSocketAddrs for Str {
173 type Iter = IntoIter<SocketAddr>;
174
175 /// Converts a string to a socket address.
176 fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
177 self.deref().to_socket_addrs()
178 }
179 }
180}
181
182impl Str {
183 /// Creates a `Str` from a static string slice.
184 ///
185 /// This method allows creating a `Str` from a string with a static lifetime,
186 /// which will be stored as a pointer to the static data without any allocation.
187 ///
188 /// # Examples
189 ///
190 /// ```
191 /// use waterui_str::Str;
192 ///
193 /// let s = Str::from_static("hello");
194 /// assert_eq!(s, "hello");
195 /// // Reference count is intentionally not exposed
196 /// ```
197 #[must_use]
198 pub const fn from_static(s: &'static str) -> Self {
199 // SAFETY: a `&'static str` never has a null data pointer, and its non-negative
200 // length is what marks this `Str` as the static representation.
201 unsafe {
202 Self {
203 ptr: NonNull::new_unchecked(s.as_ptr().cast_mut().cast::<()>()),
204 len: s.len().cast_signed(),
205 }
206 }
207 }
208
209 fn from_string(string: String) -> Self {
210 let len = string.len();
211 if len == 0 {
212 // use static empty string
213 return Self::new();
214 }
215
216 Self {
217 ptr: NonNull::from(Box::leak(Box::new(Shared::new(string)))).cast::<()>(),
218 len: -len.cast_signed(),
219 }
220 }
221
222 const fn is_shared(&self) -> bool {
223 self.len < 0
224 }
225
226 const fn as_shared(&self) -> Result<&Shared, &'static str> {
227 if !self.is_shared() {
228 // SAFETY: `len >= 0` is the static representation, so `ptr`/`len` describe
229 // the original `&'static str` — live for the program, and UTF-8 already.
230 return Err(unsafe {
231 core::str::from_utf8_unchecked(slice::from_raw_parts(
232 self.ptr.as_ptr().cast(),
233 self.len(),
234 ))
235 });
236 }
237
238 // SAFETY: `len < 0` is the shared representation, so `ptr` is the leaked
239 // `Shared` this `Str` holds a count on; the borrow is tied to `&self`.
240 unsafe { Ok(self.ptr.cast::<Shared>().as_ref()) }
241 }
242
243 /// Returns a string slice of this `Str`.
244 ///
245 /// This method works for both static and owned strings.
246 ///
247 /// # Examples
248 ///
249 /// ```
250 /// use waterui_str::Str;
251 ///
252 /// let s1 = Str::from("hello");
253 /// assert_eq!(s1.as_str(), "hello");
254 ///
255 /// let s2 = Str::from(String::from("world"));
256 /// assert_eq!(s2.as_str(), "world");
257 /// ```
258 #[must_use]
259 pub const fn as_str(&self) -> &str {
260 match self.as_shared() {
261 // SAFETY: the `Shared` outlives this borrow because `self` holds a count
262 // on it.
263 Ok(shared) => unsafe { shared.as_str() },
264 Err(str) => str,
265 }
266 }
267
268 /// Returns the length of the string, in bytes.
269 ///
270 /// # Examples
271 ///
272 /// ```
273 /// use waterui_str::Str;
274 /// let s = Str::from("hello");
275 /// assert_eq!(s.len(), 5);
276 /// ```
277 #[must_use]
278 pub const fn len(&self) -> usize {
279 self.len.unsigned_abs()
280 }
281
282 /// Returns `true` if the string has a length of zero.
283 ///
284 /// # Examples
285 ///
286 /// ```
287 /// use waterui_str::Str;
288 /// let s = Str::new();
289 /// assert!(s.is_empty());
290 /// let s2 = Str::from("not empty");
291 /// assert!(!s2.is_empty());
292 /// ```
293 #[must_use]
294 pub const fn is_empty(&self) -> bool {
295 self.len() == 0
296 }
297
298 // Intentionally no public API exposing reference counts.
299
300 /// Converts this `Str` into a `String`.
301 ///
302 /// For static strings, this will allocate a new string and copy the contents.
303 /// For owned strings, this will attempt to take ownership of the string if the reference
304 /// count is 1, or create a new copy otherwise.
305 ///
306 /// # Examples
307 ///
308 /// ```
309 /// use waterui_str::Str;
310 ///
311 /// let s1 = Str::from("static");
312 /// let s1_string = s1.into_string();
313 /// assert_eq!(s1_string, "static");
314 ///
315 /// let s2 = Str::from(String::from("owned"));
316 /// let s2_string = s2.into_string();
317 /// assert_eq!(s2_string, "owned");
318 /// ```
319 #[must_use]
320 pub fn into_string(self) -> String {
321 let this = ManuallyDrop::new(self);
322 match this.as_shared() {
323 // SAFETY: `self` is wrapped in `ManuallyDrop`, so its count is not released
324 // twice. When unique, this is the last owner and may reclaim the box;
325 // otherwise it drops its own count and copies the contents.
326 Ok(shared) => unsafe {
327 if shared.is_unique() {
328 let shared = Box::from_raw(this.ptr.cast::<Shared>().as_ptr());
329
330 shared.take()
331 } else {
332 shared.decrement_count();
333 shared.as_str().to_string()
334 }
335 },
336 Err(str) => str.to_string(),
337 }
338 }
339}
340
341impl Str {
342 /// Creates a new empty `Str`.
343 ///
344 /// This returns a static empty string reference.
345 ///
346 /// # Examples
347 ///
348 /// ```
349 /// use waterui_str::Str;
350 ///
351 /// let s = Str::new();
352 /// assert_eq!(s, "");
353 /// // Reference count is intentionally not exposed
354 /// ```
355 #[must_use]
356 pub const fn new() -> Self {
357 Self::from_static("")
358 }
359
360 /// Creates a `Str` from a vector of bytes.
361 ///
362 /// This function will attempt to convert the vector to a UTF-8 string and
363 /// wrap it in a `Str`. If the vector does not contain valid UTF-8, an error
364 /// is returned.
365 ///
366 /// # Errors
367 ///
368 /// Returns an error if the provided byte vector does not contain valid UTF-8 data.
369 ///
370 /// # Examples
371 ///
372 /// ```
373 /// use waterui_str::Str;
374 ///
375 /// let bytes = vec![104, 101, 108, 108, 111]; // "hello" in UTF-8
376 /// let s = Str::from_utf8(bytes).unwrap();
377 /// assert_eq!(s, "hello");
378 /// // Reference count is intentionally not exposed
379 ///
380 /// // Invalid UTF-8 sequence
381 /// let invalid = vec![0xFF, 0xFF];
382 /// assert!(Str::from_utf8(invalid).is_err());
383 /// ```
384 pub fn from_utf8(bytes: Vec<u8>) -> Result<Self, FromUtf8Error> {
385 String::from_utf8(bytes).map(Self::from)
386 }
387
388 /// # Safety
389 ///
390 /// This function is unsafe because it does not check that the bytes passed
391 /// to it are valid UTF-8. If this constraint is violated, it may cause
392 /// memory unsafety issues with future users of the `Str`.
393 ///
394 /// # Examples
395 ///
396 /// ```
397 /// use waterui_str::Str;
398 ///
399 /// // SAFETY: We know these bytes form valid UTF-8
400 /// let bytes = vec![104, 101, 108, 108, 111]; // "hello" in UTF-8
401 /// let s = unsafe { Str::from_utf8_unchecked(bytes) };
402 /// assert_eq!(s, "hello");
403 /// ```
404 #[must_use]
405 pub unsafe fn from_utf8_unchecked(bytes: Vec<u8>) -> Self {
406 // SAFETY: this function's own contract requires `bytes` to be valid UTF-8.
407 unsafe { Self::from(String::from_utf8_unchecked(bytes)) }
408 }
409
410 /// Applies a function to the owned string representation of this `Str`.
411 ///
412 /// This is an internal utility method used for operations that need to modify
413 /// the string contents.
414 fn handle(&mut self, f: impl FnOnce(&mut String)) {
415 let mut string = take(self).into_string();
416 f(&mut string);
417 *self = Self::from(string);
418 }
419
420 /// Appends a string to this `Str`.
421 ///
422 /// This method will convert the `Str` to an owned string if it's a static reference.
423 ///
424 /// # Examples
425 ///
426 /// ```
427 /// use waterui_str::Str;
428 ///
429 /// let mut s = Str::from("hello");
430 /// s.append(" world");
431 /// assert_eq!(s, "hello world");
432 /// ```
433 pub fn append(&mut self, s: impl AsRef<str>) {
434 let mut string = take(self).into_string();
435 string.push_str(s.as_ref());
436 *self = Self::from(string);
437 }
438}
439impl From<&'static str> for Str {
440 /// Creates a `Str` from a static string slice.
441 ///
442 /// This stores a reference to the original string without any allocation.
443 ///
444 /// # Examples
445 ///
446 /// ```
447 /// use waterui_str::Str;
448 ///
449 /// let s = Str::from("hello");
450 /// assert_eq!(s, "hello");
451 /// // Reference count is intentionally not exposed
452 /// ```
453 fn from(value: &'static str) -> Self {
454 Self::from_static(value)
455 }
456}
457
458impl From<String> for Str {
459 /// Creates a `Str` from an owned `String`.
460 ///
461 /// This will store the string in a reference-counted container.
462 ///
463 /// # Examples
464 ///
465 /// ```
466 /// use waterui_str::Str;
467 ///
468 /// let s = Str::from(String::from("hello"));
469 /// assert_eq!(s, "hello");
470 /// // Reference count is intentionally not exposed
471 /// ```
472 fn from(value: String) -> Self {
473 Self::from_string(value)
474 }
475}
476
477impl From<Str> for String {
478 fn from(value: Str) -> Self {
479 value.into_string()
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486 use alloc::vec;
487
488 #[test]
489 fn test_static_string_creation() {
490 let s = Str::from_static("hello");
491 assert_eq!(s.as_str(), "hello");
492 assert_eq!(s.len(), 5);
493 assert!(!s.is_empty());
494 // no reference count exposed
495 }
496
497 #[test]
498 fn test_owned_string_creation() {
499 let s = Str::from(String::from("hello"));
500 assert_eq!(s.as_str(), "hello");
501 assert_eq!(s.len(), 5);
502 assert!(!s.is_empty());
503 // no reference count exposed
504 }
505
506 #[test]
507 fn test_empty_string() {
508 let s = Str::new();
509 assert_eq!(s.as_str(), "");
510 assert_eq!(s.len(), 0);
511 assert!(s.is_empty());
512 // no reference count exposed
513 }
514
515 #[test]
516 fn test_static_string_clone() {
517 let s1 = Str::from_static("hello");
518 let s2 = s1.clone();
519
520 assert_eq!(s1.as_str(), "hello");
521 assert_eq!(s2.as_str(), "hello");
522 // no reference count exposed
523 }
524
525 #[test]
526 fn test_owned_string_clone() {
527 let s1 = Str::from(String::from("hello"));
528 let s2 = s1.clone();
529
530 assert_eq!(s1.as_str(), "hello");
531 assert_eq!(s2.as_str(), "hello");
532 }
533
534 #[test]
535 fn test_multiple_clones() {
536 let s1 = Str::from(String::from("test"));
537 let s2 = s1.clone();
538 let s3 = s1;
539 let s4 = s2.clone();
540
541 // no reference count exposed
542
543 drop(s4);
544 // no reference count exposed
545
546 drop(s3);
547 drop(s2);
548 // no reference count exposed
549 }
550
551 #[test]
552 fn test_reference_counting_drop() {
553 let s1 = Str::from(String::from("hello"));
554
555 {
556 let _s2 = s1;
557 } // s2 is dropped here
558
559 // no reference count exposed
560 }
561
562 #[test]
563 fn test_into_string_unique() {
564 let s = Str::from(String::from("hello"));
565 // no reference count exposed
566
567 let string = s.into_string();
568 assert_eq!(string, "hello");
569 }
570
571 #[test]
572 fn test_into_string_shared() {
573 let s1 = Str::from(String::from("hello"));
574 let _s2 = s1.clone();
575
576 let string = s1.into_string();
577 assert_eq!(string, "hello");
578 // no reference count exposed
579 }
580
581 #[test]
582 fn test_into_string_static() {
583 let s = Str::from_static("hello");
584 let string = s.into_string();
585 assert_eq!(string, "hello");
586 }
587
588 #[test]
589 fn test_from_utf8_valid() {
590 let bytes = vec![104, 101, 108, 108, 111]; // "hello"
591 let s = Str::from_utf8(bytes).unwrap();
592 assert_eq!(s.as_str(), "hello");
593 }
594
595 #[test]
596 fn test_from_utf8_invalid() {
597 let bytes = vec![0xFF, 0xFF];
598 assert!(Str::from_utf8(bytes).is_err());
599 }
600
601 #[test]
602 fn test_from_utf8_unchecked() {
603 let bytes = vec![104, 101, 108, 108, 111]; // "hello"
604 // SAFETY: `bytes` is the ASCII literal spelled out just above, so it is
605 // valid UTF-8.
606 let s = unsafe { Str::from_utf8_unchecked(bytes) };
607 assert_eq!(s.as_str(), "hello");
608 // no reference count exposed
609 }
610
611 #[test]
612 fn test_append() {
613 let mut s = Str::from("hello");
614 s.append(" world");
615 assert_eq!(s.as_str(), "hello world");
616 }
617
618 #[test]
619 fn test_append_static_to_owned() {
620 let mut s = Str::from_static("hello");
621
622 s.append(" world");
623 assert_eq!(s.as_str(), "hello world");
624 }
625
626 #[test]
627 fn test_as_bytes() {
628 let s = Str::from("hello");
629 assert_eq!(s.as_bytes(), b"hello");
630 }
631
632 #[test]
633 fn test_deref() {
634 let s = Str::from("hello");
635 assert_eq!(&*s, "hello");
636 assert_eq!(s.chars().count(), 5);
637 }
638
639 #[test]
640 fn test_empty_string_from_string() {
641 let s = Str::from(String::new());
642 assert_eq!(s.as_str(), "");
643 assert!(s.is_empty());
644 // no reference count exposed
645 }
646
647 // Memory safety tests designed for Miri
648 #[test]
649 fn test_memory_safety_clone_drop_cycles() {
650 // Test multiple clone/drop cycles to ensure no memory leaks or double-frees
651 for _ in 0..100 {
652 let s1 = Str::from(String::from("test"));
653 let s2 = s1.clone();
654 let s3 = s2.clone();
655
656 drop(s1);
657 drop(s3);
658 drop(s2);
659 }
660 }
661
662 #[test]
663 fn test_memory_safety_interleaved_operations() {
664 let mut strings = vec![];
665
666 // Create multiple strings with shared references
667 for i in 0..10 {
668 let mut content = String::from("string_");
669 content.push_str(&(i.to_string()));
670 let s = Str::from(content);
671 strings.push(s.clone());
672 strings.push(s);
673 }
674
675 // Randomly drop some strings
676 for i in (0..strings.len()).step_by(3) {
677 if i < strings.len() {
678 strings.remove(i);
679 }
680 }
681
682 // Verify remaining strings are still valid
683 for s in &strings {
684 assert!(!s.as_str().is_empty());
685 }
686 }
687
688 #[test]
689 fn test_memory_safety_reference_counting() {
690 let original = Str::from(String::from("reference test"));
691 let mut clones = vec![];
692
693 // Create many clones
694 for _ in 0..50 {
695 clones.push(original.clone());
696 }
697 assert_eq!(clones.len(), 50);
698
699 // no reference count exposed
700
701 // Drop half the clones
702 clones.truncate(25);
703 assert_eq!(clones.len(), 25);
704 // no reference count exposed
705
706 // Drop all clones
707 clones.clear();
708 assert!(clones.is_empty());
709 // no reference count exposed
710 }
711
712 #[test]
713 fn test_memory_safety_into_string_with_clones() {
714 let s1 = Str::from(String::from("unique test"));
715 let _s2 = s1.clone();
716 let _s3 = s1.clone();
717
718 // no reference count exposed
719
720 // Converting to string should not affect other references
721 let string = s1.into_string();
722 assert_eq!(string, "unique test");
723 // no reference count exposed
724 }
725
726 #[test]
727 fn test_memory_safety_unique_into_string() {
728 // Test that unique references properly transfer ownership
729 let s = Str::from(String::from("unique"));
730 // no reference count exposed
731
732 let string = s.into_string();
733 assert_eq!(string, "unique");
734 // s is consumed, can't check reference count
735 }
736
737 #[test]
738 fn test_memory_safety_static_vs_owned() {
739 let static_str = Str::from_static("static");
740 let owned_str = Str::from(String::from("owned"));
741
742 // Clone both types many times
743 let mut static_clones = vec![];
744 let mut owned_clones = vec![];
745
746 for _ in 0..100 {
747 static_clones.push(static_str.clone());
748 owned_clones.push(owned_str.clone());
749 }
750
751 // no reference count exposed
752
753 // Verify all clones work correctly
754 for clone in &static_clones {
755 assert_eq!(clone.as_str(), "static");
756 // no reference count exposed
757 }
758
759 for clone in &owned_clones {
760 assert_eq!(clone.as_str(), "owned");
761 // no reference count exposed
762 }
763 }
764
765 #[test]
766 fn test_memory_safety_mixed_operations() {
767 let mut s = Str::from_static("hello");
768 // no reference count exposed
769
770 // Convert to owned by appending
771 s.append(" world");
772 // no reference count exposed
773
774 // Clone the owned string
775 let _s2 = s.clone();
776 // no reference count exposed
777
778 // Convert back to string
779 let string = s.into_string();
780 assert_eq!(string, "hello world");
781 // no reference count exposed
782 }
783
784 #[test]
785 fn test_memory_safety_zero_length_edge_cases() {
786 // Test various ways to create empty strings
787 let empty1 = Str::new();
788 let empty2 = Str::from("");
789 let empty3 = Str::from(String::new());
790 let empty4 = Str::from_utf8(vec![]).unwrap();
791
792 assert!(empty1.is_empty());
793 assert!(empty2.is_empty());
794 assert!(empty3.is_empty());
795 assert!(empty4.is_empty());
796
797 // All empty strings should be static references
798 // no reference count exposed
799 }
800
801 #[test]
802 fn test_memory_safety_large_strings() {
803 // Test with larger strings to ensure proper memory handling
804 let large_content = "x".repeat(10000);
805 let s1 = Str::from(large_content.clone());
806 // no reference count exposed
807
808 let s2 = s1.clone();
809 // no reference count exposed
810
811 assert_eq!(s1.len(), 10000);
812 assert_eq!(s2.len(), 10000);
813 assert_eq!(s1.as_str(), large_content);
814 assert_eq!(s2.as_str(), large_content);
815 }
816
817 #[test]
818 fn test_memory_safety_concurrent_like_pattern() {
819 // Simulate concurrent-like access patterns (single-threaded but similar stress)
820 let base = Str::from(String::from("base"));
821 let mut handles = vec![];
822
823 // Create many references
824 for _ in 0..1000 {
825 handles.push(base.clone());
826 }
827
828 // no reference count exposed
829
830 // Process in chunks, dropping some while keeping others
831 for chunk in handles.chunks_mut(100) {
832 for (i, handle) in chunk.iter().enumerate() {
833 assert_eq!(handle.as_str(), "base");
834 if i.is_multiple_of(2) {
835 // Mark for keeping (we'll drop the others)
836 }
837 }
838 }
839
840 // Keep only every 3rd element
841 let mut i = 0;
842 handles.retain(|_| {
843 i += 1;
844 i % 3 == 0
845 });
846
847 // Verify reference count updated correctly
848 let _expected_count = handles.len() + 1; // +1 for base
849
850 // Verify all remaining handles are valid
851 for handle in &handles {
852 assert_eq!(handle.as_str(), "base");
853 }
854 }
855
856 #[test]
857 fn test_memory_safety_drop_order_stress() {
858 // Test various drop orders to ensure no use-after-free
859 let s1 = Str::from(String::from("original"));
860 let s2 = s1.clone();
861 let s3 = s1.clone();
862 let s4 = s2.clone();
863 let s5 = s3.clone();
864
865 // no reference count exposed
866
867 // Drop in different orders across multiple test runs
868 {
869 let temp1 = s1.clone();
870 let temp2 = s2.clone();
871 drop(temp2);
872 drop(temp1);
873 // temp1 and temp2 dropped first
874 }
875
876 // no reference count exposed
877
878 drop(s5); // Drop s5 first
879 // no reference count exposed
880
881 drop(s2); // Drop s2 (middle)
882 // no reference count exposed
883
884 drop(s1); // Drop original
885 // no reference count exposed
886
887 drop(s4); // Drop s4
888 // no reference count exposed
889
890 // s3 is the last one standing
891 assert_eq!(s3.as_str(), "original");
892 }
893
894 #[test]
895 fn test_memory_safety_ptr_stability() {
896 // Ensure string content pointer remains stable across clones
897 let s1 = Str::from(String::from("stable"));
898 let ptr1 = s1.as_str().as_ptr();
899
900 let s2 = s1.clone();
901 let ptr2 = s2.as_str().as_ptr();
902
903 // Clones should point to the same underlying data
904 assert_eq!(ptr1, ptr2);
905
906 let s3 = s2.clone();
907 let ptr3 = s3.as_str().as_ptr();
908
909 assert_eq!(ptr1, ptr3);
910 assert_eq!(ptr2, ptr3);
911
912 // Even after dropping some references, remaining should still be valid
913 drop(s1);
914 assert_eq!(s2.as_str().as_ptr(), ptr2);
915 assert_eq!(s3.as_str().as_ptr(), ptr3);
916 }
917
918 #[test]
919 fn test_memory_safety_alternating_clone_drop() {
920 let original = Str::from(String::from("alternating"));
921 let mut refs = vec![original];
922
923 // Alternating pattern: clone, clone, drop, clone, drop, etc.
924 for i in 0..100 {
925 if i % 4 == 0 || i % 4 == 1 {
926 // Clone phase
927 let new_ref = refs[0].clone();
928 refs.push(new_ref);
929 } else if i % 4 == 2 && refs.len() > 1 {
930 // Drop phase
931 refs.pop();
932 }
933
934 // Verify all remaining references are valid
935 for r in &refs {
936 assert_eq!(r.as_str(), "alternating");
937 }
938 }
939 }
940}