rune_alloc/
str.rs

1//! Utilities for the `str` primitive type.
2//!
3//! *[See also the `str` primitive type](str).*
4
5use crate::alloc::{Allocator, Global};
6use crate::borrow::TryToOwned;
7use crate::boxed::Box;
8use crate::error::Error;
9use crate::string::String;
10use crate::vec::Vec;
11use crate::Result;
12
13/// Converts a boxed slice of bytes to a boxed string slice without checking
14/// that the string contains valid UTF-8.
15///
16/// # Examples
17///
18/// ```
19/// use rune::alloc::Box;
20/// use rune::alloc::str;
21///
22/// let smile_utf8 = Box::try_from([226, 152, 186])?;
23/// let smile = unsafe { str::from_boxed_utf8_unchecked(smile_utf8) };
24///
25/// assert_eq!("☺", &*smile);
26/// # Ok::<_, rune::alloc::Error>(())
27/// ```
28///
29/// # Safety
30///
31/// The provided buffer must be valid UTF-8.
32#[must_use]
33#[inline]
34pub unsafe fn from_boxed_utf8_unchecked<A>(v: Box<[u8], A>) -> Box<str, A>
35where
36    A: Allocator,
37{
38    let (ptr, alloc) = Box::into_raw_with_allocator(v);
39    unsafe { Box::from_raw_in(ptr as *mut str, alloc) }
40}
41
42/// Converts a [`Box<str>`] into a [`String`] without copying or allocating.
43///
44/// # Examples
45///
46/// Basic usage:
47///
48/// ```
49/// use rune::alloc::String;
50/// use rune::alloc::str;
51/// use rune::alloc::prelude::*;
52///
53/// let string = String::try_from("birthday gift")?;
54/// let boxed_str = string.try_clone()?.try_into_boxed_str()?;
55///
56/// assert_eq!(str::into_string(boxed_str), string);
57/// # Ok::<_, rune::alloc::Error>(())
58/// ```
59#[must_use = "`self` will be dropped if the result is not used"]
60#[inline]
61pub fn into_string<A>(this: Box<str, A>) -> String<A>
62where
63    A: Allocator,
64{
65    let slice = Box::<[u8], A>::from(this);
66    let vec = crate::slice::into_vec(slice);
67    unsafe { String::<A>::from_utf8_unchecked(vec) }
68}
69
70/// Replaces all matches of a pattern with another string.
71///
72/// `replace` creates a new [`String`], and copies the data from this string slice into it.
73/// While doing so, it attempts to find matches of a pattern. If it finds any, it
74/// replaces them with the replacement string slice.
75///
76/// # Examples
77///
78/// Basic usage:
79///
80/// ```
81/// let s = "this is old";
82///
83/// assert_eq!("this is new", rune::alloc::str::replace(s, "old", "new")?);
84/// assert_eq!("than an old", rune::alloc::str::replace(s, "is", "an")?);
85/// # Ok::<_, rune::alloc::Error>(())
86/// ```
87///
88/// When the pattern doesn't match, it returns this string slice as [`String`]:
89///
90/// ```
91/// let s = "this is old";
92/// assert_eq!(s, rune::alloc::str::replace(s, "cookie monster", "little lamb")?);
93/// # Ok::<_, rune::alloc::Error>(())
94/// ```
95///
96/// Single ascii-character replacements are optimized for performance:
97///
98/// ```
99/// assert_eq!("say", rune::alloc::str::replace("bay", "b", "s")?);
100/// # Ok::<_, rune::alloc::Error>(())
101/// ```
102pub fn replace(string: &str, from: &str, to: &str) -> Result<String> {
103    // Fast path for replacing a single ASCII character with another.
104    if let (&[from_byte], &[to_byte]) = (from.as_bytes(), to.as_bytes()) {
105        return unsafe { replace_ascii(string.as_bytes(), from_byte, to_byte) };
106    }
107
108    // Set result capacity to self.len() when from.len() <= to.len()
109    let default_capacity = if from.len() <= to.len() {
110        string.len()
111    } else {
112        0
113    };
114
115    let mut result = String::try_with_capacity(default_capacity)?;
116    let mut last_end = 0;
117
118    for (start, part) in string.match_indices(from) {
119        result.try_push_str(unsafe { string.get_unchecked(last_end..start) })?;
120        result.try_push_str(to)?;
121        last_end = start + part.len();
122    }
123
124    result.try_push_str(unsafe { string.get_unchecked(last_end..string.len()) })?;
125    Ok(result)
126}
127
128unsafe fn replace_ascii(bytes: &[u8], from: u8, to: u8) -> Result<String> {
129    let mut result = Vec::try_with_capacity(bytes.len())?;
130
131    for &b in bytes {
132        if b == from {
133            result.try_push(to)?;
134        } else {
135            result.try_push(b)?;
136        }
137    }
138
139    // SAFETY: We replaced ascii with ascii on valid utf8 strings.
140    Ok(String::from_utf8_unchecked(result))
141}
142
143impl TryToOwned for str {
144    type Owned = String<Global>;
145
146    #[inline]
147    fn try_to_owned(&self) -> Result<String<Global>, Error> {
148        Ok(unsafe { String::from_utf8_unchecked(self.as_bytes().try_to_owned()?) })
149    }
150}