2 use crate::fmt::Formatter;
4 use crate::iter::FusedIterator;
6 use super::from_utf8_unchecked;
7 use super::validations::utf8_char_width;
9 /// An item returned by the [`Utf8Chunks`] iterator.
11 /// A `Utf8Chunk` stores a sequence of [`u8`] up to the first broken character
12 /// when decoding a UTF-8 string.
17 /// #![feature(utf8_chunks)]
19 /// use std::str::Utf8Chunks;
21 /// // An invalid UTF-8 string
22 /// let bytes = b"foo\xF1\x80bar";
24 /// // Decode the first `Utf8Chunk`
25 /// let chunk = Utf8Chunks::new(bytes).next().unwrap();
27 /// // The first three characters are valid UTF-8
28 /// assert_eq!("foo", chunk.valid());
30 /// // The fourth character is broken
31 /// assert_eq!(b"\xF1\x80", chunk.invalid());
33 #[unstable(feature = "utf8_chunks", issue = "99543")]
34 #[derive(Clone, Debug, PartialEq, Eq)]
35 pub struct Utf8Chunk<'a> {
40 impl<'a> Utf8Chunk<'a> {
41 /// Returns the next validated UTF-8 substring.
43 /// This substring can be empty at the start of the string or between
44 /// broken UTF-8 characters.
46 #[unstable(feature = "utf8_chunks", issue = "99543")]
47 pub fn valid(&self) -> &'a str {
51 /// Returns the invalid sequence that caused a failure.
53 /// The returned slice will have a maximum length of 3 and starts after the
54 /// substring given by [`valid`]. Decoding will resume after this sequence.
56 /// If empty, this is the last chunk in the string. If non-empty, an
57 /// unexpected byte was encountered or the end of the input was reached
60 /// Lossy decoding would replace this sequence with [`U+FFFD REPLACEMENT
63 /// [`valid`]: Self::valid
64 /// [`U+FFFD REPLACEMENT CHARACTER`]: crate::char::REPLACEMENT_CHARACTER
66 #[unstable(feature = "utf8_chunks", issue = "99543")]
67 pub fn invalid(&self) -> &'a [u8] {
73 #[unstable(feature = "str_internals", issue = "none")]
74 pub struct Debug<'a>(&'a [u8]);
76 #[unstable(feature = "str_internals", issue = "none")]
77 impl fmt::Debug for Debug<'_> {
78 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
81 for chunk in Utf8Chunks::new(self.0) {
83 // Here we partially parse UTF-8 again which is suboptimal.
85 let valid = chunk.valid();
87 for (i, c) in valid.char_indices() {
88 let esc = c.escape_debug();
89 // If char needs escaping, flush backlog so far and write, else skip
91 f.write_str(&valid[from..i])?;
95 from = i + c.len_utf8();
98 f.write_str(&valid[from..])?;
101 // Broken parts of string as hex escape.
102 for &b in chunk.invalid() {
103 write!(f, "\\x{:02X}", b)?;
111 /// An iterator used to decode a slice of mostly UTF-8 bytes to string slices
112 /// ([`&str`]) and byte slices ([`&[u8]`][byteslice]).
114 /// If you want a simple conversion from UTF-8 byte slices to string slices,
115 /// [`from_utf8`] is easier to use.
117 /// [byteslice]: slice
118 /// [`from_utf8`]: super::from_utf8
122 /// This can be used to create functionality similar to
123 /// [`String::from_utf8_lossy`] without allocating heap memory:
126 /// #![feature(utf8_chunks)]
128 /// use std::str::Utf8Chunks;
130 /// fn from_utf8_lossy<F>(input: &[u8], mut push: F) where F: FnMut(&str) {
131 /// for chunk in Utf8Chunks::new(input) {
132 /// push(chunk.valid());
134 /// if !chunk.invalid().is_empty() {
135 /// push("\u{FFFD}");
141 /// [`String::from_utf8_lossy`]: ../../std/string/struct.String.html#method.from_utf8_lossy
142 #[must_use = "iterators are lazy and do nothing unless consumed"]
143 #[unstable(feature = "utf8_chunks", issue = "99543")]
145 pub struct Utf8Chunks<'a> {
149 impl<'a> Utf8Chunks<'a> {
150 /// Creates a new iterator to decode the bytes.
151 #[unstable(feature = "utf8_chunks", issue = "99543")]
152 pub fn new(bytes: &'a [u8]) -> Self {
153 Self { source: bytes }
157 #[unstable(feature = "str_internals", issue = "none")]
158 pub fn debug(&self) -> Debug<'_> {
163 #[unstable(feature = "utf8_chunks", issue = "99543")]
164 impl<'a> Iterator for Utf8Chunks<'a> {
165 type Item = Utf8Chunk<'a>;
167 fn next(&mut self) -> Option<Utf8Chunk<'a>> {
168 if self.source.is_empty() {
172 const TAG_CONT_U8: u8 = 128;
173 fn safe_get(xs: &[u8], i: usize) -> u8 {
174 *xs.get(i).unwrap_or(&0)
178 let mut valid_up_to = 0;
179 while i < self.source.len() {
180 // SAFETY: `i < self.source.len()` per previous line.
181 // For some reason the following are both significantly slower:
182 // while let Some(&byte) = self.source.get(i) {
183 // while let Some(byte) = self.source.get(i).copied() {
184 let byte = unsafe { *self.source.get_unchecked(i) };
188 // This could be a `1 => ...` case in the match below, but for
189 // the common case of all-ASCII inputs, we bypass loading the
190 // sizeable UTF8_CHAR_WIDTH table into cache.
192 let w = utf8_char_width(byte);
196 if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
202 match (byte, safe_get(self.source, i)) {
203 (0xE0, 0xA0..=0xBF) => (),
204 (0xE1..=0xEC, 0x80..=0xBF) => (),
205 (0xED, 0x80..=0x9F) => (),
206 (0xEE..=0xEF, 0x80..=0xBF) => (),
210 if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
216 match (byte, safe_get(self.source, i)) {
217 (0xF0, 0x90..=0xBF) => (),
218 (0xF1..=0xF3, 0x80..=0xBF) => (),
219 (0xF4, 0x80..=0x8F) => (),
223 if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
227 if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
239 // SAFETY: `i <= self.source.len()` because it is only ever incremented
240 // via `i += 1` and in between every single one of those increments, `i`
241 // is compared against `self.source.len()`. That happens either
242 // literally by `i < self.source.len()` in the while-loop's condition,
243 // or indirectly by `safe_get(self.source, i) & 192 != TAG_CONT_U8`. The
244 // loop is terminated as soon as the latest `i += 1` has made `i` no
245 // longer less than `self.source.len()`, which means it'll be at most
246 // equal to `self.source.len()`.
247 let (inspected, remaining) = unsafe { self.source.split_at_unchecked(i) };
248 self.source = remaining;
250 // SAFETY: `valid_up_to <= i` because it is only ever assigned via
251 // `valid_up_to = i` and `i` only increases.
252 let (valid, invalid) = unsafe { inspected.split_at_unchecked(valid_up_to) };
255 // SAFETY: All bytes up to `valid_up_to` are valid UTF-8.
256 valid: unsafe { from_utf8_unchecked(valid) },
262 #[unstable(feature = "utf8_chunks", issue = "99543")]
263 impl FusedIterator for Utf8Chunks<'_> {}
265 #[unstable(feature = "utf8_chunks", issue = "99543")]
266 impl fmt::Debug for Utf8Chunks<'_> {
267 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
268 f.debug_struct("Utf8Chunks").field("source", &self.debug()).finish()