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 #[ferrocene::prevalidated]
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)]
73#[ferrocene::prevalidated]
74pub struct Utf8Chunk<'a> {
75 valid: &'a str,
76 invalid: &'a [u8],
77}
78
79impl<'a> Utf8Chunk<'a> {
80 /// Returns the next validated UTF-8 substring.
81 ///
82 /// This substring can be empty at the start of the string or between
83 /// broken UTF-8 characters.
84 #[must_use]
85 #[stable(feature = "utf8_chunks", since = "1.79.0")]
86 #[ferrocene::prevalidated]
87 pub fn valid(&self) -> &'a str {
88 self.valid
89 }
90
91 /// Returns the invalid sequence that caused a failure.
92 ///
93 /// The returned slice will have a maximum length of 3 and starts after the
94 /// substring given by [`valid`]. Decoding will resume after this sequence.
95 ///
96 /// If empty, this is the last chunk in the string. If non-empty, an
97 /// unexpected byte was encountered or the end of the input was reached
98 /// unexpectedly.
99 ///
100 /// Lossy decoding would replace this sequence with [`U+FFFD REPLACEMENT
101 /// CHARACTER`].
102 ///
103 /// [`valid`]: Self::valid
104 /// [`U+FFFD REPLACEMENT CHARACTER`]: char::REPLACEMENT_CHARACTER
105 #[must_use]
106 #[stable(feature = "utf8_chunks", since = "1.79.0")]
107 #[ferrocene::prevalidated]
108 pub fn invalid(&self) -> &'a [u8] {
109 self.invalid
110 }
111}
112
113#[must_use]
114#[unstable(feature = "str_internals", issue = "none")]
115#[ferrocene::prevalidated]
116pub struct Debug<'a>(&'a [u8]);
117
118#[unstable(feature = "str_internals", issue = "none")]
119impl fmt::Debug for Debug<'_> {
120 #[ferrocene::prevalidated]
121 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
122 f.write_char('"')?;
123
124 for chunk in self.0.utf8_chunks() {
125 // Valid part.
126 // Here we partially parse UTF-8 again which is suboptimal.
127 {
128 let valid = chunk.valid();
129 let mut from = 0;
130 for (i, c) in valid.char_indices() {
131 let esc = c.escape_debug_ext(EscapeDebugExtArgs {
132 escape_single_quote: false,
133 escape_double_quote: true,
134 });
135 // If char needs escaping, flush backlog so far and write, else skip
136 if esc.len() != 1 {
137 f.write_str(&valid[from..i])?;
138 for c in esc {
139 f.write_char(c)?;
140 }
141 from = i + c.len_utf8();
142 }
143 }
144 f.write_str(&valid[from..])?;
145 }
146
147 // Broken parts of string as hex escape.
148 for &b in chunk.invalid() {
149 write!(f, "\\x{:02X}", b)?;
150 }
151 }
152
153 f.write_char('"')
154 }
155}
156
157/// An iterator used to decode a slice of mostly UTF-8 bytes to string slices
158/// ([`&str`]) and byte slices ([`&[u8]`][byteslice]).
159///
160/// This struct is created by the [`utf8_chunks`] method on bytes slices.
161/// If you want a simple conversion from UTF-8 byte slices to string slices,
162/// [`from_utf8`] is easier to use.
163///
164/// See the [`Utf8Chunk`] type for documentation of the items yielded by this iterator.
165///
166/// [byteslice]: slice
167/// [`utf8_chunks`]: slice::utf8_chunks
168/// [`from_utf8`]: super::from_utf8
169///
170/// # Examples
171///
172/// This can be used to create functionality similar to
173/// [`String::from_utf8_lossy`] without allocating heap memory:
174///
175/// ```
176/// fn from_utf8_lossy<F>(input: &[u8], mut push: F) where F: FnMut(&str) {
177/// for chunk in input.utf8_chunks() {
178/// push(chunk.valid());
179///
180/// if !chunk.invalid().is_empty() {
181/// push("\u{FFFD}");
182/// }
183/// }
184/// }
185/// ```
186///
187/// [`String::from_utf8_lossy`]: ../../std/string/struct.String.html#method.from_utf8_lossy
188#[must_use = "iterators are lazy and do nothing unless consumed"]
189#[stable(feature = "utf8_chunks", since = "1.79.0")]
190#[derive(Clone)]
191#[ferrocene::prevalidated]
192pub struct Utf8Chunks<'a> {
193 source: &'a [u8],
194}
195
196impl<'a> Utf8Chunks<'a> {
197 #[doc(hidden)]
198 #[unstable(feature = "str_internals", issue = "none")]
199 #[ferrocene::prevalidated]
200 pub fn debug(&self) -> Debug<'_> {
201 Debug(self.source)
202 }
203}
204
205#[stable(feature = "utf8_chunks", since = "1.79.0")]
206impl<'a> Iterator for Utf8Chunks<'a> {
207 type Item = Utf8Chunk<'a>;
208
209 #[ferrocene::prevalidated]
210 fn next(&mut self) -> Option<Utf8Chunk<'a>> {
211 if self.source.is_empty() {
212 return None;
213 }
214
215 const TAG_CONT_U8: u8 = 128;
216 #[ferrocene::prevalidated]
217 fn safe_get(xs: &[u8], i: usize) -> u8 {
218 *xs.get(i).unwrap_or(&0)
219 }
220
221 let mut i = 0;
222 let mut valid_up_to = 0;
223 while let Some(byte) = self.source.get(i).copied() {
224 i += 1;
225
226 if byte < 128 {
227 // This could be a `1 => ...` case in the match below, but for
228 // the common case of all-ASCII inputs, we bypass loading the
229 // sizeable UTF8_CHAR_WIDTH table into cache.
230 } else {
231 let w = utf8_char_width(byte);
232
233 match w {
234 2 => {
235 if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
236 break;
237 }
238 i += 1;
239 }
240 3 => {
241 match (byte, safe_get(self.source, i)) {
242 (0xE0, 0xA0..=0xBF) => (),
243 (0xE1..=0xEC, 0x80..=0xBF) => (),
244 (0xED, 0x80..=0x9F) => (),
245 (0xEE..=0xEF, 0x80..=0xBF) => (),
246 _ => break,
247 }
248 i += 1;
249 if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
250 break;
251 }
252 i += 1;
253 }
254 4 => {
255 match (byte, safe_get(self.source, i)) {
256 (0xF0, 0x90..=0xBF) => (),
257 (0xF1..=0xF3, 0x80..=0xBF) => (),
258 (0xF4, 0x80..=0x8F) => (),
259 _ => break,
260 }
261 i += 1;
262 if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
263 break;
264 }
265 i += 1;
266 if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
267 break;
268 }
269 i += 1;
270 }
271 _ => break,
272 }
273 }
274
275 valid_up_to = i;
276 }
277
278 // SAFETY: `i <= self.source.len()` because it is only ever incremented
279 // via `i += 1` and in between every single one of those increments, `i`
280 // is compared against `self.source.len()`. That happens either
281 // literally by `i < self.source.len()` in the while-loop's condition,
282 // or indirectly by `safe_get(self.source, i) & 192 != TAG_CONT_U8`. The
283 // loop is terminated as soon as the latest `i += 1` has made `i` no
284 // longer less than `self.source.len()`, which means it'll be at most
285 // equal to `self.source.len()`.
286 let (inspected, remaining) = unsafe { self.source.split_at_unchecked(i) };
287 self.source = remaining;
288
289 // SAFETY: `valid_up_to <= i` because it is only ever assigned via
290 // `valid_up_to = i` and `i` only increases.
291 let (valid, invalid) = unsafe { inspected.split_at_unchecked(valid_up_to) };
292
293 Some(Utf8Chunk {
294 // SAFETY: All bytes up to `valid_up_to` are valid UTF-8.
295 valid: unsafe { from_utf8_unchecked(valid) },
296 invalid,
297 })
298 }
299}
300
301#[stable(feature = "utf8_chunks", since = "1.79.0")]
302impl FusedIterator for Utf8Chunks<'_> {}
303
304#[stable(feature = "utf8_chunks", since = "1.79.0")]
305impl fmt::Debug for Utf8Chunks<'_> {
306 #[ferrocene::prevalidated]
307 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
308 f.debug_struct("Utf8Chunks").field("source", &self.debug()).finish()
309 }
310}