Skip to main content

core/str/
lossy.rs

1use super::char::EscapeDebugExtArgs;
2use super::from_utf8_unchecked;
3use super::validations::utf8_char_width;
4use crate::fmt;
5use crate::fmt::{Formatter, Write};
6#[cfg(not(feature = "ferrocene_subset"))]
7use crate::iter::FusedIterator;
8
9impl [u8] {
10    /// Creates an iterator over the contiguous valid UTF-8 ranges of this
11    /// slice, and the non-UTF-8 fragments in between.
12    ///
13    /// See the [`Utf8Chunk`] type for documentation of the items yielded by this iterator.
14    ///
15    /// # Examples
16    ///
17    /// This function formats arbitrary but mostly-UTF-8 bytes into Rust source
18    /// code in the form of a C-string literal (`c"..."`).
19    ///
20    /// ```
21    /// use std::fmt::Write as _;
22    ///
23    /// pub fn cstr_literal(bytes: &[u8]) -> String {
24    ///     let mut repr = String::new();
25    ///     repr.push_str("c\"");
26    ///     for chunk in bytes.utf8_chunks() {
27    ///         for ch in chunk.valid().chars() {
28    ///             // Escapes \0, \t, \r, \n, \\, \', \", and uses \u{...} for non-printable characters.
29    ///             write!(repr, "{}", ch.escape_debug()).unwrap();
30    ///         }
31    ///         for byte in chunk.invalid() {
32    ///             write!(repr, "\\x{:02X}", byte).unwrap();
33    ///         }
34    ///     }
35    ///     repr.push('"');
36    ///     repr
37    /// }
38    ///
39    /// fn main() {
40    ///     let lit = cstr_literal(b"\xferris the \xf0\x9f\xa6\x80\x07");
41    ///     let expected = stringify!(c"\xFErris the 🦀\u{7}");
42    ///     assert_eq!(lit, expected);
43    /// }
44    /// ```
45    #[stable(feature = "utf8_chunks", since = "1.79.0")]
46    pub fn utf8_chunks(&self) -> Utf8Chunks<'_> {
47        Utf8Chunks { source: self }
48    }
49}
50
51/// An item returned by the [`Utf8Chunks`] iterator.
52///
53/// A `Utf8Chunk` stores a sequence of [`u8`] up to the first broken character
54/// when decoding a UTF-8 string.
55///
56/// # Examples
57///
58/// ```
59/// // An invalid UTF-8 string
60/// let bytes = b"foo\xF1\x80bar";
61///
62/// // Decode the first `Utf8Chunk`
63/// let chunk = bytes.utf8_chunks().next().unwrap();
64///
65/// // The first three characters are valid UTF-8
66/// assert_eq!("foo", chunk.valid());
67///
68/// // The fourth character is broken
69/// assert_eq!(b"\xF1\x80", chunk.invalid());
70/// ```
71#[stable(feature = "utf8_chunks", since = "1.79.0")]
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct Utf8Chunk<'a> {
74    valid: &'a str,
75    invalid: &'a [u8],
76}
77
78impl<'a> Utf8Chunk<'a> {
79    /// Returns the next validated UTF-8 substring.
80    ///
81    /// This substring can be empty at the start of the string or between
82    /// broken UTF-8 characters.
83    #[must_use]
84    #[stable(feature = "utf8_chunks", since = "1.79.0")]
85    pub fn valid(&self) -> &'a str {
86        self.valid
87    }
88
89    /// Returns the invalid sequence that caused a failure.
90    ///
91    /// The returned slice will have a maximum length of 3 and starts after the
92    /// substring given by [`valid`]. Decoding will resume after this sequence.
93    ///
94    /// If empty, this is the last chunk in the string. If non-empty, an
95    /// unexpected byte was encountered or the end of the input was reached
96    /// unexpectedly.
97    ///
98    /// Lossy decoding would replace this sequence with [`U+FFFD REPLACEMENT
99    /// CHARACTER`].
100    ///
101    /// [`valid`]: Self::valid
102    /// [`U+FFFD REPLACEMENT CHARACTER`]: crate::char::REPLACEMENT_CHARACTER
103    #[must_use]
104    #[stable(feature = "utf8_chunks", since = "1.79.0")]
105    pub fn invalid(&self) -> &'a [u8] {
106        self.invalid
107    }
108}
109
110#[must_use]
111#[unstable(feature = "str_internals", issue = "none")]
112pub struct Debug<'a>(&'a [u8]);
113
114#[unstable(feature = "str_internals", issue = "none")]
115impl fmt::Debug for Debug<'_> {
116    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117        f.write_char('"')?;
118
119        for chunk in self.0.utf8_chunks() {
120            // Valid part.
121            // Here we partially parse UTF-8 again which is suboptimal.
122            {
123                let valid = chunk.valid();
124                let mut from = 0;
125                for (i, c) in valid.char_indices() {
126                    let esc = c.escape_debug_ext(EscapeDebugExtArgs {
127                        escape_grapheme_extended: true,
128                        escape_single_quote: false,
129                        escape_double_quote: true,
130                    });
131                    // If char needs escaping, flush backlog so far and write, else skip
132                    if esc.len() != 1 {
133                        f.write_str(&valid[from..i])?;
134                        for c in esc {
135                            f.write_char(c)?;
136                        }
137                        from = i + c.len_utf8();
138                    }
139                }
140                f.write_str(&valid[from..])?;
141            }
142
143            // Broken parts of string as hex escape.
144            for &b in chunk.invalid() {
145                write!(f, "\\x{:02X}", b)?;
146            }
147        }
148
149        f.write_char('"')
150    }
151}
152
153/// An iterator used to decode a slice of mostly UTF-8 bytes to string slices
154/// ([`&str`]) and byte slices ([`&[u8]`][byteslice]).
155///
156/// This struct is created by the [`utf8_chunks`] method on bytes slices.
157/// If you want a simple conversion from UTF-8 byte slices to string slices,
158/// [`from_utf8`] is easier to use.
159///
160/// See the [`Utf8Chunk`] type for documentation of the items yielded by this iterator.
161///
162/// [byteslice]: slice
163/// [`utf8_chunks`]: slice::utf8_chunks
164/// [`from_utf8`]: super::from_utf8
165///
166/// # Examples
167///
168/// This can be used to create functionality similar to
169/// [`String::from_utf8_lossy`] without allocating heap memory:
170///
171/// ```
172/// fn from_utf8_lossy<F>(input: &[u8], mut push: F) where F: FnMut(&str) {
173///     for chunk in input.utf8_chunks() {
174///         push(chunk.valid());
175///
176///         if !chunk.invalid().is_empty() {
177///             push("\u{FFFD}");
178///         }
179///     }
180/// }
181/// ```
182///
183/// [`String::from_utf8_lossy`]: ../../std/string/struct.String.html#method.from_utf8_lossy
184#[must_use = "iterators are lazy and do nothing unless consumed"]
185#[stable(feature = "utf8_chunks", since = "1.79.0")]
186#[derive(Clone)]
187pub struct Utf8Chunks<'a> {
188    source: &'a [u8],
189}
190
191impl<'a> Utf8Chunks<'a> {
192    #[doc(hidden)]
193    #[unstable(feature = "str_internals", issue = "none")]
194    pub fn debug(&self) -> Debug<'_> {
195        Debug(self.source)
196    }
197}
198
199#[stable(feature = "utf8_chunks", since = "1.79.0")]
200impl<'a> Iterator for Utf8Chunks<'a> {
201    type Item = Utf8Chunk<'a>;
202
203    fn next(&mut self) -> Option<Utf8Chunk<'a>> {
204        if self.source.is_empty() {
205            return None;
206        }
207
208        const TAG_CONT_U8: u8 = 128;
209        fn safe_get(xs: &[u8], i: usize) -> u8 {
210            *xs.get(i).unwrap_or(&0)
211        }
212
213        let mut i = 0;
214        let mut valid_up_to = 0;
215        while i < self.source.len() {
216            // SAFETY: `i < self.source.len()` per previous line.
217            // For some reason the following are both significantly slower:
218            // while let Some(&byte) = self.source.get(i) {
219            // while let Some(byte) = self.source.get(i).copied() {
220            let byte = unsafe { *self.source.get_unchecked(i) };
221            i += 1;
222
223            if byte < 128 {
224                // This could be a `1 => ...` case in the match below, but for
225                // the common case of all-ASCII inputs, we bypass loading the
226                // sizeable UTF8_CHAR_WIDTH table into cache.
227            } else {
228                let w = utf8_char_width(byte);
229
230                match w {
231                    2 => {
232                        if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
233                            break;
234                        }
235                        i += 1;
236                    }
237                    3 => {
238                        match (byte, safe_get(self.source, i)) {
239                            (0xE0, 0xA0..=0xBF) => (),
240                            (0xE1..=0xEC, 0x80..=0xBF) => (),
241                            (0xED, 0x80..=0x9F) => (),
242                            (0xEE..=0xEF, 0x80..=0xBF) => (),
243                            _ => break,
244                        }
245                        i += 1;
246                        if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
247                            break;
248                        }
249                        i += 1;
250                    }
251                    4 => {
252                        match (byte, safe_get(self.source, i)) {
253                            (0xF0, 0x90..=0xBF) => (),
254                            (0xF1..=0xF3, 0x80..=0xBF) => (),
255                            (0xF4, 0x80..=0x8F) => (),
256                            _ => break,
257                        }
258                        i += 1;
259                        if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
260                            break;
261                        }
262                        i += 1;
263                        if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
264                            break;
265                        }
266                        i += 1;
267                    }
268                    _ => break,
269                }
270            }
271
272            valid_up_to = i;
273        }
274
275        // SAFETY: `i <= self.source.len()` because it is only ever incremented
276        // via `i += 1` and in between every single one of those increments, `i`
277        // is compared against `self.source.len()`. That happens either
278        // literally by `i < self.source.len()` in the while-loop's condition,
279        // or indirectly by `safe_get(self.source, i) & 192 != TAG_CONT_U8`. The
280        // loop is terminated as soon as the latest `i += 1` has made `i` no
281        // longer less than `self.source.len()`, which means it'll be at most
282        // equal to `self.source.len()`.
283        let (inspected, remaining) = unsafe { self.source.split_at_unchecked(i) };
284        self.source = remaining;
285
286        // SAFETY: `valid_up_to <= i` because it is only ever assigned via
287        // `valid_up_to = i` and `i` only increases.
288        let (valid, invalid) = unsafe { inspected.split_at_unchecked(valid_up_to) };
289
290        Some(Utf8Chunk {
291            // SAFETY: All bytes up to `valid_up_to` are valid UTF-8.
292            valid: unsafe { from_utf8_unchecked(valid) },
293            invalid,
294        })
295    }
296}
297
298#[cfg(not(feature = "ferrocene_subset"))]
299#[stable(feature = "utf8_chunks", since = "1.79.0")]
300impl FusedIterator for Utf8Chunks<'_> {}
301
302#[stable(feature = "utf8_chunks", since = "1.79.0")]
303impl fmt::Debug for Utf8Chunks<'_> {
304    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
305        f.debug_struct("Utf8Chunks").field("source", &self.debug()).finish()
306    }
307}