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