Skip to main content

smol_bytes/
utf8_buffer.rs

1use core::{borrow::Borrow, ops::RangeBounds, str};
2
3use super::{
4  buffer::Buffer,
5  error::*,
6  utf8_buf::{Utf8Buf, Utf8BufMut, range_bounds_to_start_end, sealed, validate_char_boundary},
7  utf8_drain::Utf8Drain,
8  utf8_edit::{RawUtf8Storage, RetainGuard, replace_bytes as raw_replace_bytes},
9};
10
11mod cmp;
12mod fmt;
13mod from;
14mod ops;
15
16#[cfg(feature = "arbitrary")]
17mod arbitrary;
18#[cfg(feature = "borsh")]
19mod borsh;
20#[cfg(all(feature = "quickcheck", any(feature = "std", feature = "alloc")))]
21mod quickcheck;
22#[cfg(feature = "serde")]
23mod serde;
24
25#[cfg(feature = "pyo3")]
26mod python;
27
28#[cfg(feature = "wasm")]
29mod wasm;
30
31#[doc = "UTF-8 validated wrapper around `Buffer` with a String-like interface."]
32#[doc = ""]
33#[doc = "Guarantees valid UTF-8. Split/slice operations check char boundaries."]
34#[doc = "Fixed inline capacity of 62 bytes."]
35#[cfg_attr(not(feature = "wasm"), doc = "")]
36#[cfg_attr(not(feature = "wasm"), doc = "# Examples")]
37#[cfg_attr(not(feature = "wasm"), doc = "")]
38#[cfg_attr(not(feature = "wasm"), doc = "```")]
39#[cfg_attr(not(feature = "wasm"), doc = "use smol_bytes::Utf8Buffer;")]
40#[cfg_attr(not(feature = "wasm"), doc = "")]
41#[cfg_attr(not(feature = "wasm"), doc = "let mut buf = Utf8Buffer::new();")]
42#[cfg_attr(not(feature = "wasm"), doc = "buf.push_str(\"Hello\");")]
43#[cfg_attr(not(feature = "wasm"), doc = "buf.push(' ');")]
44#[cfg_attr(not(feature = "wasm"), doc = "buf.push_str(\"world!\");")]
45#[cfg_attr(
46  not(feature = "wasm"),
47  doc = "assert_eq!(buf.as_str(), \"Hello world!\");"
48)]
49#[cfg_attr(not(feature = "wasm"), doc = "```")]
50#[cfg_attr(feature = "wasm", doc = "")]
51#[cfg_attr(feature = "wasm", doc = "@example")]
52#[cfg_attr(feature = "wasm", doc = "```typescript")]
53#[cfg_attr(feature = "wasm", doc = "import { Utf8Buffer } from 'smol-bytes';")]
54#[cfg_attr(feature = "wasm", doc = "const buf = new Utf8Buffer();")]
55#[cfg_attr(feature = "wasm", doc = "buf.pushStr('hello world');")]
56#[cfg_attr(
57  feature = "wasm",
58  doc = "console.log(buf.toString()); // 'hello world'"
59)]
60#[cfg_attr(feature = "wasm", doc = "```")]
61#[derive(Clone, Copy)]
62#[cfg_attr(feature = "pyo3", ::pyo3::prelude::pyclass)]
63#[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
64pub struct Utf8Buffer {
65  inner: Buffer,
66}
67
68impl Default for Utf8Buffer {
69  #[cfg_attr(not(coverage), inline(always))]
70  fn default() -> Self {
71    Self::new()
72  }
73}
74
75impl sealed::Sealed for Utf8Buffer {}
76
77impl core::fmt::Write for Utf8Buffer {
78  fn write_str(&mut self, s: &str) -> core::fmt::Result {
79    self.try_push_str(s).map_err(|_| core::fmt::Error)
80  }
81}
82
83struct FormatWriteAdapter<'a> {
84  candidate: &'a mut Utf8Buffer,
85  capacity_error: Option<TryPutError>,
86}
87
88impl core::fmt::Write for FormatWriteAdapter<'_> {
89  fn write_str(&mut self, s: &str) -> core::fmt::Result {
90    match self.candidate.try_push_str(s) {
91      Ok(()) => Ok(()),
92      Err(error) => {
93        if self.capacity_error.is_none() {
94          self.capacity_error = Some(error);
95        }
96        Err(core::fmt::Error)
97      }
98    }
99  }
100}
101
102impl Utf8Buffer {
103  /// Creates a new, empty `Utf8Buffer`.
104  ///
105  /// # Examples
106  ///
107  /// ```
108  /// use smol_bytes::Utf8Buffer;
109  ///
110  /// let buf = Utf8Buffer::new();
111  /// assert!(buf.is_empty());
112  /// ```
113  #[cfg_attr(not(coverage), inline(always))]
114  pub const fn new() -> Self {
115    Self {
116      inner: Buffer::new(),
117    }
118  }
119
120  /// Creates a `Utf8Buffer` from a static string slice.
121  ///
122  /// Unlike the shared immutable UTF-8 wrapper's `from_static` constructor,
123  /// this method **copies** the bytes into the inline buffer — `Utf8Buffer`
124  /// has no heap variant. It exists as a const constructor for parity.
125  ///
126  /// # Panics
127  ///
128  /// Panics at compile time (via `const` assertion) if `s.len() > 62`.
129  ///
130  /// # Examples
131  ///
132  /// ```
133  /// use smol_bytes::Utf8Buffer;
134  ///
135  /// const GREETING: Utf8Buffer = Utf8Buffer::from_static("hello");
136  /// assert_eq!(GREETING.as_str(), "hello");
137  /// ```
138  #[cfg_attr(not(coverage), inline(always))]
139  pub const fn from_static(s: &'static str) -> Self {
140    let bytes = s.as_bytes();
141    assert!(
142      bytes.len() <= crate::INLINE_CAP,
143      "string too large for Utf8Buffer inline capacity"
144    );
145    // SAFETY: length checked against INLINE_CAP above; input is already valid UTF-8.
146    Self {
147      inner: unsafe { Buffer::copy_from_slice(bytes) },
148    }
149  }
150
151  /// Returns a reference to the inner `Buffer`.
152  #[cfg_attr(not(coverage), inline(always))]
153  pub const fn as_inner(&self) -> &Buffer {
154    &self.inner
155  }
156
157  /// Consumes `self` and returns the inner `Buffer`.
158  #[cfg_attr(not(coverage), inline(always))]
159  pub const fn into_inner(self) -> Buffer {
160    self.inner
161  }
162
163  /// Returns the contents as a string slice.
164  ///
165  /// # Examples
166  ///
167  /// ```
168  /// use smol_bytes::Utf8Buffer;
169  ///
170  /// let buf = Utf8Buffer::try_from("hello").unwrap();
171  /// assert_eq!(buf.as_str(), "hello");
172  /// ```
173  #[cfg_attr(not(coverage), inline(always))]
174  pub fn as_str(&self) -> &str {
175    // SAFETY: Utf8Buffer guarantees valid UTF-8
176    unsafe { str::from_utf8_unchecked(self.inner.as_slice()) }
177  }
178
179  /// Returns the contents as a mutable string slice.
180  ///
181  /// Mutations must preserve UTF-8 validity and the byte length of edited
182  /// ranges, as required by [`prim@str`].
183  pub fn as_mut_str(&mut self) -> &mut str {
184    str::from_utf8_mut(self.inner.as_mut_slice()).expect("UTF-8 invariant")
185  }
186
187  /// Returns the length of the buffer in bytes.
188  ///
189  /// # Examples
190  ///
191  /// ```
192  /// use smol_bytes::Utf8Buffer;
193  ///
194  /// let buf = Utf8Buffer::try_from("hello").unwrap();
195  /// assert_eq!(buf.len(), 5);
196  /// ```
197  #[cfg_attr(not(coverage), inline(always))]
198  pub const fn len(&self) -> usize {
199    self.inner.len()
200  }
201
202  /// Returns `true` if the buffer has a length of 0.
203  ///
204  /// # Examples
205  ///
206  /// ```
207  /// use smol_bytes::Utf8Buffer;
208  ///
209  /// let buf = Utf8Buffer::new();
210  /// assert!(buf.is_empty());
211  /// ```
212  #[cfg_attr(not(coverage), inline(always))]
213  pub const fn is_empty(&self) -> bool {
214    self.inner.is_empty()
215  }
216
217  /// Returns the number of bytes stored in the buffer.
218  ///
219  /// This is equivalent to [`len`](Self::len).
220  ///
221  /// # Examples
222  ///
223  /// ```
224  /// use smol_bytes::Utf8Buffer;
225  ///
226  /// let buf = Utf8Buffer::try_from("hi").unwrap();
227  /// assert_eq!(buf.remaining(), 2);
228  /// ```
229  #[cfg_attr(not(coverage), inline(always))]
230  pub const fn remaining(&self) -> usize {
231    self.inner.remaining()
232  }
233
234  /// Returns the capacity of the buffer.
235  #[cfg_attr(not(coverage), inline(always))]
236  pub const fn capacity(&self) -> usize {
237    self.inner.capacity()
238  }
239
240  /// Reclaims consumed fixed-buffer prefix space without allocating.
241  ///
242  /// Returns `true` when the writable tail can hold `additional` bytes. On
243  /// `false`, this value is unchanged.
244  ///
245  /// ```
246  /// use smol_bytes::Utf8Buffer;
247  ///
248  /// let mut text = Utf8Buffer::try_from("abcd").unwrap();
249  /// let _ = text.split_to(2);
250  /// assert!(text.try_reclaim(60));
251  /// assert_eq!(text.as_str(), "cd");
252  /// ```
253  pub fn try_reclaim(&mut self, additional: usize) -> bool {
254    self.inner.try_reclaim(additional)
255  }
256
257  /// Inserts `s` at a UTF-8 character boundary.
258  ///
259  /// # Examples
260  ///
261  /// ```
262  /// use smol_bytes::Utf8Buffer;
263  ///
264  /// let mut text = Utf8Buffer::try_from("aé").unwrap();
265  /// text.insert_str(1, "🦀");
266  /// assert_eq!(text.as_str(), "a🦀é");
267  /// ```
268  ///
269  /// # Panics
270  ///
271  /// Panics if `idx` is invalid or the inserted bytes do not fit.
272  pub fn insert_str(&mut self, idx: usize, s: &str) {
273    self
274      .try_insert_str(idx, s)
275      .unwrap_or_else(|error| panic!("insert_str failed: {error}"));
276  }
277
278  /// Tries to insert `s` at a UTF-8 character boundary.
279  ///
280  /// The buffer is unchanged on error. A capacity error reports only the
281  /// positive amount by which the edit would grow the current view.
282  pub fn try_insert_str(&mut self, idx: usize, s: &str) -> Result<(), Utf8EditError> {
283    validate_char_boundary(self.as_str(), idx).map_err(Utf8EditError::Index)?;
284    self.replace_range_validated(idx, idx, s)
285  }
286
287  /// Inserts a Unicode scalar value at a UTF-8 character boundary.
288  ///
289  /// # Panics
290  ///
291  /// Panics if `idx` is invalid or the encoded scalar does not fit.
292  pub fn insert(&mut self, idx: usize, ch: char) {
293    self
294      .try_insert(idx, ch)
295      .unwrap_or_else(|error| panic!("insert failed: {error}"));
296  }
297
298  /// Tries to insert a Unicode scalar value at a UTF-8 character boundary.
299  pub fn try_insert(&mut self, idx: usize, ch: char) -> Result<(), Utf8EditError> {
300    let mut encoded = [0_u8; 4];
301    self.try_insert_str(idx, ch.encode_utf8(&mut encoded))
302  }
303
304  /// Replaces a UTF-8 character-boundary range with `replacement`.
305  ///
306  /// # Panics
307  ///
308  /// Panics if the range is invalid or the replacement does not fit.
309  pub fn replace_range(&mut self, range: impl RangeBounds<usize>, replacement: &str) {
310    self
311      .try_replace_range(range, replacement)
312      .unwrap_or_else(|error| panic!("replace_range failed: {error}"));
313  }
314
315  /// Tries to replace a UTF-8 character-boundary range with `replacement`.
316  ///
317  /// Validation and capacity checks happen before mutation, so this buffer is
318  /// unchanged when an error is returned.
319  pub fn try_replace_range(
320    &mut self,
321    range: impl RangeBounds<usize>,
322    replacement: &str,
323  ) -> Result<(), Utf8EditError> {
324    let (start, end) =
325      range_bounds_to_start_end(self.as_str(), range).map_err(Utf8EditError::Index)?;
326    self.replace_range_validated(start, end, replacement)
327  }
328
329  /// Appends a copy of the specified UTF-8 character-boundary range.
330  ///
331  /// # Panics
332  ///
333  /// Panics if the range is invalid or the copied bytes do not fit in the
334  /// remaining tail capacity.
335  pub fn extend_from_within(&mut self, range: impl RangeBounds<usize>) {
336    self
337      .try_extend_from_within(range)
338      .unwrap_or_else(|error| panic!("extend_from_within failed: {error}"));
339  }
340
341  /// Tries to append a copy of the specified UTF-8 character-boundary range.
342  ///
343  /// Validation and capacity preflight happen before mutation. Consumed prefix
344  /// storage is not reclaimed implicitly.
345  pub fn try_extend_from_within(
346    &mut self,
347    range: impl RangeBounds<usize>,
348  ) -> Result<(), Utf8EditError> {
349    let old_len = self.len();
350    let (start, end) = range_bounds_to_start_end(self.as_str(), range)?;
351    let count = end - start;
352    let new_len = old_len.checked_add(count).expect("buffer length overflow");
353    let available = self.inner.remaining_mut();
354    if count > available {
355      return Err(
356        TryPutError {
357          requested: count,
358          available,
359        }
360        .into(),
361      );
362    }
363
364    if count == 0 {
365      return Ok(());
366    }
367
368    self.inner.resize(new_len, 0);
369    self.inner.as_mut_slice().copy_within(start..end, old_len);
370    Ok(())
371  }
372
373  fn replace_range_validated(
374    &mut self,
375    start: usize,
376    end: usize,
377    replacement: &str,
378  ) -> Result<(), Utf8EditError> {
379    let old_len = self.inner.len();
380    let removed = end - start;
381    let growth = replacement.len().saturating_sub(removed);
382    let available = self.inner.capacity() - old_len;
383    if growth > available {
384      return Err(
385        TryPutError {
386          requested: growth,
387          available,
388        }
389        .into(),
390      );
391    }
392
393    let mut storage = RawUtf8Storage::fixed(&mut self.inner);
394    raw_replace_bytes(&mut storage, start, end, replacement.as_bytes());
395    Ok(())
396  }
397
398  /// Removes and returns the Unicode scalar value at `idx`.
399  ///
400  /// # Panics
401  ///
402  /// Panics if `idx` is outside the visible string or is not a UTF-8
403  /// character boundary.
404  pub fn remove(&mut self, idx: usize) -> char {
405    self
406      .try_remove(idx)
407      .unwrap_or_else(|error| panic!("remove failed: {error}"))
408  }
409
410  /// Tries to remove and return the Unicode scalar value at `idx`.
411  pub fn try_remove(&mut self, idx: usize) -> Result<char, Utf8IndexError> {
412    let len = self.len();
413    if idx >= len {
414      return Err(OutOfBounds::new(idx, len).into());
415    }
416    validate_char_boundary(self.as_str(), idx)?;
417    let ch = self.as_str()[idx..]
418      .chars()
419      .next()
420      .expect("index below string length has a scalar");
421    self
422      .replace_range_validated(idx, idx + ch.len_utf8(), "")
423      .expect("removing a scalar cannot exceed fixed capacity");
424    Ok(ch)
425  }
426
427  /// Retains only the characters for which `predicate` returns `true`.
428  ///
429  /// The predicate runs once per character in original order without
430  /// allocating. If it panics, decisions already returned from the predicate
431  /// are kept, while the current character and unvisited suffix remain.
432  ///
433  /// ```
434  /// use smol_bytes::Utf8Buffer;
435  ///
436  /// let mut text = Utf8Buffer::try_from("aé🦀").unwrap();
437  /// text.retain(|character| character != 'é');
438  /// assert_eq!(text.as_str(), "a🦀");
439  /// ```
440  pub fn retain<F>(&mut self, mut predicate: F)
441  where
442    F: FnMut(char) -> bool,
443  {
444    let mut guard = RetainGuard::new(RawUtf8Storage::fixed(&mut self.inner));
445    guard.retain(&mut predicate);
446  }
447
448  /// Returns an iterator over a range that is removed when the iterator drops.
449  ///
450  /// # Panics
451  ///
452  /// Panics if the range is out of bounds or not on UTF-8 character boundaries.
453  pub fn drain(&mut self, range: impl RangeBounds<usize>) -> Utf8Drain<'_> {
454    self.try_drain(range).expect("drain range failed")
455  }
456
457  /// Tries to return an iterator over a range removed when it drops.
458  ///
459  /// The selected range is not removed eagerly. Dropping this iterator removes
460  /// the entire original range, even if it was only partly consumed.
461  pub fn try_drain(
462    &mut self,
463    range: impl RangeBounds<usize>,
464  ) -> Result<Utf8Drain<'_>, Utf8IndexError> {
465    let (start, end) = range_bounds_to_start_end(self.as_str(), range)?;
466    Ok(Utf8Drain::new(
467      RawUtf8Storage::fixed(&mut self.inner),
468      start,
469      end,
470    ))
471  }
472
473  /// Appends a character to the buffer.
474  ///
475  /// `Utf8Buffer` is backed by a fixed 62-byte inline [`Buffer`]; this
476  /// method panics when the character would not fit. Use [`try_push`]
477  /// for a fallible variant.
478  ///
479  /// [`try_push`]: Self::try_push
480  ///
481  /// # Panics
482  ///
483  /// Panics if the buffer's remaining capacity is less than `ch.len_utf8()`.
484  ///
485  /// # Examples
486  ///
487  /// ```
488  /// use smol_bytes::Utf8Buffer;
489  ///
490  /// let mut buf = Utf8Buffer::new();
491  /// buf.push('a');
492  /// buf.push('é'); // 2-byte char
493  /// assert_eq!(buf.as_str(), "aé");
494  /// ```
495  pub fn push(&mut self, ch: char) {
496    <Self as Utf8BufMut>::push(self, ch)
497  }
498
499  /// Appends a string slice to the buffer.
500  ///
501  /// `Utf8Buffer` is backed by a fixed 62-byte inline [`Buffer`]; this
502  /// method panics when the string would not fit. Use [`try_push_str`]
503  /// for a fallible variant.
504  ///
505  /// [`try_push_str`]: Self::try_push_str
506  ///
507  /// # Panics
508  ///
509  /// Panics if the buffer's remaining capacity is less than `s.len()`.
510  ///
511  /// # Examples
512  ///
513  /// ```
514  /// use smol_bytes::Utf8Buffer;
515  ///
516  /// let mut buf = Utf8Buffer::new();
517  /// buf.push_str("hello");
518  /// assert_eq!(buf.as_str(), "hello");
519  /// ```
520  pub fn push_str(&mut self, s: &str) {
521    <Self as Utf8BufMut>::push_str(self, s)
522  }
523
524  /// Tries to append a character to the buffer.
525  ///
526  /// # Errors
527  ///
528  /// Returns [`TryPutError`] if the buffer's remaining capacity is less
529  /// than `ch.len_utf8()`. On error the buffer is unchanged.
530  ///
531  /// # Examples
532  ///
533  /// ```
534  /// use smol_bytes::Utf8Buffer;
535  ///
536  /// let mut buf = Utf8Buffer::try_from("a".repeat(60).as_str()).unwrap();
537  /// assert!(buf.try_push('a').is_ok());        // 61 bytes
538  /// assert!(buf.try_push('a').is_ok());        // 62 bytes
539  /// assert!(buf.try_push('a').is_err());       // full
540  /// ```
541  pub fn try_push(&mut self, ch: char) -> Result<(), TryPutError> {
542    let mut buf = [0u8; 4];
543    let s = ch.encode_utf8(&mut buf);
544    self.inner.try_put_slice(s.as_bytes())
545  }
546
547  /// Tries to append a string slice to the buffer.
548  ///
549  /// # Errors
550  ///
551  /// Returns [`TryPutError`] if the buffer's remaining capacity is less
552  /// than `s.len()`. On error the buffer is unchanged.
553  ///
554  /// # Examples
555  ///
556  /// ```
557  /// use smol_bytes::Utf8Buffer;
558  ///
559  /// let mut buf = Utf8Buffer::new();
560  /// assert!(buf.try_push_str("hello").is_ok());
561  /// assert_eq!(buf.as_str(), "hello");
562  /// ```
563  pub fn try_push_str(&mut self, s: &str) -> Result<(), TryPutError> {
564    self.inner.try_put_slice(s.as_bytes())
565  }
566
567  /// Formats `args` into this buffer without changing it on failure.
568  ///
569  /// Capacity failures report the first formatter chunk that did not fit. If
570  /// a formatter swallows that writer failure and returns success, the
571  /// capacity error still takes precedence.
572  pub fn try_write_fmt(&mut self, args: core::fmt::Arguments<'_>) -> Result<(), Utf8FormatError> {
573    let mut candidate = *self;
574    let (format_result, capacity_error) = {
575      let mut writer = FormatWriteAdapter {
576        candidate: &mut candidate,
577        capacity_error: None,
578      };
579      let result = core::fmt::write(&mut writer, args);
580      (result, writer.capacity_error)
581    };
582
583    if let Some(error) = capacity_error {
584      return Err(error.into());
585    }
586    if let Err(error) = format_result {
587      return Err(error.into());
588    }
589
590    *self = candidate;
591    Ok(())
592  }
593
594  /// Clears the buffer, removing all contents.
595  pub fn clear(&mut self) {
596    self.inner.clear();
597  }
598
599  /// Truncates the buffer to the specified length.
600  ///
601  /// # Panics
602  ///
603  /// Panics if `new_len` does not lie on a UTF-8 character boundary.
604  pub fn truncate(&mut self, new_len: usize) {
605    self
606      .try_truncate(new_len)
607      .expect("new_len must lie on a UTF-8 character boundary");
608  }
609
610  /// Truncates the buffer at a UTF-8 character boundary.
611  pub fn try_truncate(&mut self, new_len: usize) -> Result<(), Utf8IndexError> {
612    <Self as Utf8BufMut>::try_truncate(self, new_len)
613  }
614
615  /// Removes and returns the last Unicode scalar value.
616  ///
617  /// This operates on Unicode scalar values, not grapheme clusters, and never
618  /// allocates.
619  ///
620  /// ```
621  /// use smol_bytes::Utf8Buffer;
622  ///
623  /// let mut text = Utf8Buffer::try_from("aé").unwrap();
624  /// assert_eq!(text.pop(), Some('é'));
625  /// assert_eq!(text.as_str(), "a");
626  /// ```
627  pub fn pop(&mut self) -> Option<char> {
628    let ch = self.as_str().chars().next_back()?;
629    self.truncate(self.len() - ch.len_utf8());
630    Some(ch)
631  }
632
633  /// Splits the buffer at `at`, returning the head `[0, at)` and leaving
634  /// `self` with `[at, len)`.
635  ///
636  /// # Panics
637  ///
638  /// Panics if `at` is out of bounds or not on a UTF-8 character boundary.
639  ///
640  /// # Examples
641  ///
642  /// ```
643  /// use smol_bytes::Utf8Buffer;
644  ///
645  /// let mut buf = Utf8Buffer::try_from("hello world").unwrap();
646  /// let head = buf.split_to(5);
647  /// assert_eq!(head.as_str(), "hello");
648  /// assert_eq!(buf.as_str(), " world");
649  /// ```
650  pub fn split_to(&mut self, at: usize) -> Self {
651    <Self as Utf8Buf>::split_to(self, at)
652  }
653
654  /// Tries to split the buffer at `at`.
655  ///
656  /// # Errors
657  ///
658  /// Returns [`Utf8IndexError::InvalidCharBoundary`] if `at` is not on a
659  /// character boundary, or [`Utf8IndexError::IndexOutOfBounds`] if `at > len`.
660  pub fn try_split_to(&mut self, at: usize) -> Result<Self, Utf8IndexError> {
661    <Self as Utf8Buf>::try_split_to(self, at)
662  }
663
664  /// Splits the buffer at `at`, returning the tail `[at, len)` and leaving
665  /// `self` with `[0, at)`.
666  ///
667  /// # Panics
668  ///
669  /// Panics if `at` is out of bounds or not on a UTF-8 character boundary.
670  ///
671  /// # Examples
672  ///
673  /// ```
674  /// use smol_bytes::Utf8Buffer;
675  ///
676  /// let mut buf = Utf8Buffer::try_from("hello world").unwrap();
677  /// let tail = buf.split_off(6);
678  /// assert_eq!(buf.as_str(), "hello ");
679  /// assert_eq!(tail.as_str(), "world");
680  /// ```
681  pub fn split_off(&mut self, at: usize) -> Self {
682    <Self as Utf8Buf>::split_off(self, at)
683  }
684
685  /// Tries to split the buffer at `at`, returning the tail.
686  ///
687  /// # Errors
688  ///
689  /// Returns [`Utf8IndexError::InvalidCharBoundary`] if `at` is not on a
690  /// character boundary, or [`Utf8IndexError::IndexOutOfBounds`] if `at > len`.
691  pub fn try_split_off(&mut self, at: usize) -> Result<Self, Utf8IndexError> {
692    <Self as Utf8Buf>::try_split_off(self, at)
693  }
694
695  /// Returns a copy of a sub-range of the buffer.
696  ///
697  /// # Panics
698  ///
699  /// Panics if the range is out of bounds or not on UTF-8 character boundaries.
700  ///
701  /// # Examples
702  ///
703  /// ```
704  /// use smol_bytes::Utf8Buffer;
705  ///
706  /// let buf = Utf8Buffer::try_from("hello world").unwrap();
707  /// let slice = buf.slice(0..5);
708  /// assert_eq!(slice.as_str(), "hello");
709  /// ```
710  pub fn slice(&self, range: impl core::ops::RangeBounds<usize>) -> Self {
711    <Self as Utf8Buf>::slice(self, range)
712  }
713
714  /// Tries to return a copy of a sub-range of the buffer.
715  ///
716  /// # Errors
717  ///
718  /// Returns [`Utf8IndexError`] if the range is out of bounds or not on
719  /// character boundaries.
720  pub fn try_slice(
721    &self,
722    range: impl core::ops::RangeBounds<usize>,
723  ) -> Result<Self, Utf8IndexError> {
724    <Self as Utf8Buf>::try_slice(self, range)
725  }
726}
727
728impl AsRef<str> for Utf8Buffer {
729  #[cfg_attr(not(coverage), inline(always))]
730  fn as_ref(&self) -> &str {
731    self.as_str()
732  }
733}
734
735impl AsRef<[u8]> for Utf8Buffer {
736  #[cfg_attr(not(coverage), inline(always))]
737  fn as_ref(&self) -> &[u8] {
738    self.as_str().as_bytes()
739  }
740}
741
742impl Borrow<str> for Utf8Buffer {
743  #[cfg_attr(not(coverage), inline(always))]
744  fn borrow(&self) -> &str {
745    self.as_str()
746  }
747}
748
749impl core::ops::Deref for Utf8Buffer {
750  type Target = str;
751
752  #[cfg_attr(not(coverage), inline(always))]
753  fn deref(&self) -> &Self::Target {
754    self.as_str()
755  }
756}
757
758#[cfg(feature = "std")]
759impl AsRef<std::ffi::OsStr> for Utf8Buffer {
760  fn as_ref(&self) -> &std::ffi::OsStr {
761    std::ffi::OsStr::new(self.as_str())
762  }
763}
764
765#[cfg(feature = "std")]
766impl AsRef<std::path::Path> for Utf8Buffer {
767  fn as_ref(&self) -> &std::path::Path {
768    std::path::Path::new(self.as_str())
769  }
770}
771
772impl Utf8Buf for Utf8Buffer {
773  fn try_split_to(&mut self, at: usize) -> Result<Self, Utf8IndexError> {
774    validate_char_boundary(self.as_str(), at)?;
775
776    Ok(Self {
777      inner: self.inner.try_split_to(at).expect("already checked bounds"),
778    })
779  }
780
781  fn try_split_off(&mut self, at: usize) -> Result<Self, Utf8IndexError> {
782    validate_char_boundary(self.as_str(), at)?;
783
784    Ok(Self {
785      inner: self
786        .inner
787        .try_split_off(at)
788        .expect("already checked bounds"),
789    })
790  }
791
792  fn try_slice(&self, range: impl RangeBounds<usize>) -> Result<Self, Utf8IndexError> {
793    let (start, end) = range_bounds_to_start_end(self.as_str(), range)?;
794
795    Ok(Self {
796      inner: self
797        .inner
798        .try_slice(start..end)
799        .expect("already checked bounds"),
800    })
801  }
802}
803
804impl Utf8BufMut for Utf8Buffer {
805  type CapacityError = TryPutError;
806
807  fn try_truncate(&mut self, new_len: usize) -> Result<(), Utf8IndexError> {
808    if new_len >= self.len() {
809      return Ok(());
810    }
811    if !self.as_str().is_char_boundary(new_len) {
812      return Err(Utf8IndexError::InvalidCharBoundary { index: new_len });
813    }
814    self.inner.truncate(new_len);
815    Ok(())
816  }
817
818  fn try_push(&mut self, ch: char) -> Result<(), Self::CapacityError> {
819    let mut buf = [0u8; 4];
820    let s = ch.encode_utf8(&mut buf);
821    self.inner.try_put_slice(s.as_bytes())
822  }
823
824  fn try_push_str(&mut self, s: &str) -> Result<(), Self::CapacityError> {
825    self.inner.try_put_slice(s.as_bytes())
826  }
827
828  fn clear(&mut self) {
829    self.inner.clear();
830  }
831}