Skip to main content

smol_bytes/
utf8_bytes_mut.rs

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