Skip to main content

smol_bytes/
utf8_bytes_mut.rs

1use core::{borrow::Borrow, ops::RangeBounds, str};
2
3use super::{
4  BytesMut,
5  error::*,
6  utf8_buf::{Utf8Buf, Utf8BufMut},
7};
8use bytes::BufMut;
9
10mod cmp;
11mod fmt;
12mod from;
13mod iter;
14mod ops;
15
16#[cfg(feature = "arbitrary")]
17mod arbitrary;
18#[cfg(feature = "borsh")]
19mod borsh;
20#[cfg(feature = "quickcheck")]
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 = "Growable mutable UTF-8 string with inline/heap storage."]
32#[doc = ""]
33#[doc = "Guarantees valid UTF-8. Split operations check char boundaries."]
34#[cfg_attr(not(feature = "wasm"), doc = "")]
35#[cfg_attr(not(feature = "wasm"), doc = "# Examples")]
36#[cfg_attr(not(feature = "wasm"), doc = "")]
37#[cfg_attr(not(feature = "wasm"), doc = "```")]
38#[cfg_attr(not(feature = "wasm"), doc = "use smol_bytes::Utf8BytesMut;")]
39#[cfg_attr(not(feature = "wasm"), doc = "")]
40#[cfg_attr(not(feature = "wasm"), doc = "let mut buf = Utf8BytesMut::new();")]
41#[cfg_attr(not(feature = "wasm"), doc = "buf.push_str(\"Hello\");")]
42#[cfg_attr(not(feature = "wasm"), doc = "buf.push(' ');")]
43#[cfg_attr(not(feature = "wasm"), doc = "buf.push_str(\"world!\");")]
44#[cfg_attr(
45  not(feature = "wasm"),
46  doc = "assert_eq!(buf.as_str(), \"Hello world!\");"
47)]
48#[cfg_attr(not(feature = "wasm"), doc = "```")]
49#[cfg_attr(feature = "wasm", doc = "")]
50#[cfg_attr(feature = "wasm", doc = "@example")]
51#[cfg_attr(feature = "wasm", doc = "```typescript")]
52#[cfg_attr(feature = "wasm", doc = "import { Utf8BytesMut } from 'smol-bytes';")]
53#[cfg_attr(feature = "wasm", doc = "const buf = new Utf8BytesMut();")]
54#[cfg_attr(feature = "wasm", doc = "buf.pushStr('hello world');")]
55#[cfg_attr(
56  feature = "wasm",
57  doc = "console.log(buf.toString()); // 'hello world'"
58)]
59#[cfg_attr(feature = "wasm", doc = "```")]
60#[derive(Clone)]
61#[cfg_attr(feature = "pyo3", ::pyo3::prelude::pyclass(from_py_object))]
62#[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
63pub struct Utf8BytesMut {
64  pub(crate) inner: BytesMut,
65}
66
67impl Default for Utf8BytesMut {
68  fn default() -> Self {
69    Self::new()
70  }
71}
72
73impl core::fmt::Write for Utf8BytesMut {
74  #[inline(always)]
75  fn write_str(&mut self, s: &str) -> core::fmt::Result {
76    self.push_str(s);
77    Ok(())
78  }
79}
80
81impl Utf8BytesMut {
82  /// Creates a new, empty `Utf8BytesMut`.
83  ///
84  /// # Examples
85  ///
86  /// ```
87  /// use smol_bytes::Utf8BytesMut;
88  ///
89  /// const EMPTY: Utf8BytesMut = Utf8BytesMut::new();
90  /// assert!(EMPTY.is_empty());
91  /// ```
92  #[inline(always)]
93  pub const fn new() -> Self {
94    Self {
95      inner: BytesMut::new(),
96    }
97  }
98
99  /// Creates a new `Utf8BytesMut` with the specified capacity.
100  ///
101  /// # Examples
102  ///
103  /// ```
104  /// use smol_bytes::Utf8BytesMut;
105  ///
106  /// let buf = Utf8BytesMut::with_capacity(100);
107  /// assert!(buf.capacity() >= 100);
108  /// ```
109  pub fn with_capacity(capacity: usize) -> Self {
110    Self {
111      inner: BytesMut::with_capacity(capacity),
112    }
113  }
114
115  /// Returns a reference to the inner `BytesMut`.
116  ///
117  /// Note: named `as_inner` to avoid confusion with `str::as_bytes`/
118  /// `String::as_bytes_mut`, which users might expect to return `&[u8]`.
119  #[inline(always)]
120  pub const fn as_inner(&self) -> &BytesMut {
121    &self.inner
122  }
123
124  /// Consumes `self` and returns the inner `BytesMut`.
125  #[inline(always)]
126  pub fn into_inner(self) -> BytesMut {
127    self.inner
128  }
129
130  /// Returns the contents as a string slice.
131  ///
132  /// # Examples
133  ///
134  /// ```
135  /// use smol_bytes::Utf8BytesMut;
136  ///
137  /// let buf = Utf8BytesMut::from("hello");
138  /// assert_eq!(buf.as_str(), "hello");
139  /// ```
140  #[inline(always)]
141  pub fn as_str(&self) -> &str {
142    // SAFETY: Utf8BytesMut guarantees valid UTF-8
143    unsafe { str::from_utf8_unchecked(self.inner.as_ref()) }
144  }
145
146  /// Returns the length in bytes.
147  #[inline(always)]
148  pub fn len(&self) -> usize {
149    self.inner.len()
150  }
151
152  /// Returns `true` if the buffer has length 0.
153  #[cfg_attr(not(coverage), inline(always))]
154  pub fn is_empty(&self) -> bool {
155    self.inner.is_empty()
156  }
157
158  /// Returns the number of bytes the buffer can hold without reallocating.
159  ///
160  /// # Examples
161  ///
162  /// ```
163  /// use smol_bytes::Utf8BytesMut;
164  ///
165  /// let buf = Utf8BytesMut::with_capacity(128);
166  /// assert!(buf.capacity() >= 128);
167  /// ```
168  #[inline]
169  pub fn capacity(&self) -> usize {
170    self.inner.capacity()
171  }
172
173  /// Returns `true` if the underlying bytes are stored inline (≤ 62 bytes).
174  ///
175  /// # Examples
176  ///
177  /// ```
178  /// use smol_bytes::Utf8BytesMut;
179  ///
180  /// let small = Utf8BytesMut::from("hi");
181  /// assert!(small.is_inline());
182  /// ```
183  #[inline]
184  pub fn is_inline(&self) -> bool {
185    self.inner.is_inline()
186  }
187
188  /// Returns `true` if the underlying bytes are heap-allocated.
189  ///
190  /// # Examples
191  ///
192  /// ```
193  /// use smol_bytes::Utf8BytesMut;
194  ///
195  /// let big = Utf8BytesMut::with_capacity(128);
196  /// assert!(big.is_heap());
197  /// ```
198  #[inline]
199  pub fn is_heap(&self) -> bool {
200    self.inner.is_heap()
201  }
202
203  /// Appends a character to the buffer.
204  ///
205  /// # Examples
206  ///
207  /// ```
208  /// use smol_bytes::Utf8BytesMut;
209  ///
210  /// let mut buf = Utf8BytesMut::new();
211  /// buf.push('a');
212  /// buf.push('b');
213  /// assert_eq!(buf.as_str(), "ab");
214  /// ```
215  pub fn push(&mut self, ch: char) {
216    <Self as Utf8BufMut>::push(self, ch)
217  }
218
219  /// Appends a string slice to the buffer.
220  ///
221  /// # Examples
222  ///
223  /// ```
224  /// use smol_bytes::Utf8BytesMut;
225  ///
226  /// let mut buf = Utf8BytesMut::new();
227  /// buf.push_str("hello");
228  /// assert_eq!(buf.as_str(), "hello");
229  /// ```
230  pub fn push_str(&mut self, s: &str) {
231    <Self as Utf8BufMut>::push_str(self, s)
232  }
233
234  /// Clears the buffer, removing all contents.
235  pub fn clear(&mut self) {
236    <Self as Utf8BufMut>::clear(self)
237  }
238
239  /// Truncates the buffer to the specified length.
240  ///
241  /// # Panics
242  ///
243  /// Panics if `new_len` does not lie on a UTF-8 character boundary.
244  pub fn truncate(&mut self, new_len: usize) {
245    self
246      .try_truncate(new_len)
247      .expect("new_len must lie on a UTF-8 character boundary");
248  }
249
250  /// Truncates the buffer at a UTF-8 character boundary.
251  pub fn try_truncate(&mut self, new_len: usize) -> Result<(), Utf8Error> {
252    <Self as Utf8BufMut>::try_truncate(self, new_len)
253  }
254
255  /// Freezes this mutable value using the shared immutable strategy.
256  pub fn freeze_shared(self) -> crate::shared::Utf8Bytes {
257    crate::utf8_bytes::Utf8Bytes {
258      inner: self.inner.freeze_shared(),
259    }
260  }
261
262  /// Freezes this mutable value using the compact immutable strategy.
263  pub fn freeze_compact(self) -> crate::compact::Utf8Bytes {
264    crate::utf8_bytes::Utf8Bytes {
265      inner: self.inner.freeze_compact(),
266    }
267  }
268
269  /// Reserves capacity for at least `additional` more bytes.
270  ///
271  /// # Examples
272  ///
273  /// ```
274  /// use smol_bytes::Utf8BytesMut;
275  ///
276  /// let mut buf = Utf8BytesMut::new();
277  /// buf.reserve(100);
278  /// assert!(buf.capacity() >= 100);
279  /// ```
280  pub fn reserve(&mut self, additional: usize) {
281    self.inner.reserve(additional);
282  }
283
284  /// Splits the buffer at the given index.
285  ///
286  /// # Panics
287  ///
288  /// Panics if `at` is not on a character boundary or is out of bounds.
289  ///
290  /// # Examples
291  ///
292  /// ```
293  /// use smol_bytes::Utf8BytesMut;
294  ///
295  /// let mut buf = Utf8BytesMut::from("hello world");
296  /// let head = buf.split_to(5);
297  ///
298  /// assert_eq!(head.as_str(), "hello");
299  /// assert_eq!(buf.as_str(), " world");
300  /// ```
301  pub fn split_to(&mut self, at: usize) -> Self {
302    self.try_split_to(at).expect("split_to failed")
303  }
304
305  /// Tries to split the buffer at the given index.
306  ///
307  /// # Errors
308  ///
309  /// Returns an error if `at` is out of bounds or not on a character boundary.
310  pub fn try_split_to(&mut self, at: usize) -> Result<Self, Utf8Error> {
311    <Self as Utf8Buf>::try_split_to(self, at)
312  }
313
314  /// Splits the buffer at the given index, returning the tail.
315  ///
316  /// # Panics
317  ///
318  /// Panics if `at` is not on a character boundary or is out of bounds.
319  ///
320  /// # Examples
321  ///
322  /// ```
323  /// use smol_bytes::Utf8BytesMut;
324  ///
325  /// let mut buf = Utf8BytesMut::from("hello world");
326  /// let tail = buf.split_off(6);
327  ///
328  /// assert_eq!(buf.as_str(), "hello ");
329  /// assert_eq!(tail.as_str(), "world");
330  /// ```
331  pub fn split_off(&mut self, at: usize) -> Self {
332    self.try_split_off(at).expect("split_off failed")
333  }
334
335  /// Tries to split the buffer at the given index, returning the tail.
336  ///
337  /// # Errors
338  ///
339  /// Returns an error if `at` is out of bounds or not on a character boundary.
340  pub fn try_split_off(&mut self, at: usize) -> Result<Self, Utf8Error> {
341    <Self as Utf8Buf>::try_split_off(self, at)
342  }
343
344  /// Removes all bytes from the buffer and returns them in a new buffer.
345  ///
346  /// Afterwards, `self` will be empty, retaining any additional capacity
347  /// it had before the split (see [`bytes::BytesMut::split`]).
348  ///
349  /// # Examples
350  ///
351  /// ```
352  /// use smol_bytes::Utf8BytesMut;
353  ///
354  /// let mut buf = Utf8BytesMut::with_capacity(128);
355  /// buf.push_str("hello");
356  /// let data = buf.split();
357  ///
358  /// assert_eq!(data.as_str(), "hello");
359  /// assert_eq!(buf.len(), 0);
360  /// ```
361  pub fn split(&mut self) -> Self {
362    let inner = self
363      .inner
364      .try_split()
365      .expect("split always succeeds")
366      .unwrap_or_else(BytesMut::from);
367    Self { inner }
368  }
369
370  /// Attempts to merge another buffer back into this one.
371  ///
372  /// Returns `None` on success. Returns `Some(other)` when the buffers
373  /// cannot be efficiently merged — in particular, **both buffers must be
374  /// heap-allocated** (see [`BytesMut::unsplit`]). If either side is
375  /// inline, `unsplit` leaves `self` unchanged and hands `other` back.
376  ///
377  /// # Examples
378  ///
379  /// Heap buffers merge successfully:
380  ///
381  /// ```
382  /// use smol_bytes::Utf8BytesMut;
383  ///
384  /// let mut buf = Utf8BytesMut::with_capacity(128);
385  /// buf.push_str("hello world");
386  /// let tail = buf.split_off(5);
387  /// assert!(buf.unsplit(tail).is_none());
388  /// assert_eq!(buf.as_str(), "hello world");
389  /// ```
390  ///
391  /// Inline buffers fail to merge:
392  ///
393  /// ```
394  /// use smol_bytes::Utf8BytesMut;
395  ///
396  /// let mut buf = Utf8BytesMut::from("hi"); // inline
397  /// let tail = Utf8BytesMut::from("!");
398  /// assert!(buf.unsplit(tail).is_some()); // returned unchanged
399  /// ```
400  pub fn unsplit(&mut self, other: Self) -> Option<Self> {
401    self.inner.unsplit(other.inner).map(|inner| Self { inner })
402  }
403
404  /// Returns a slice of this buffer.
405  ///
406  /// # Panics
407  ///
408  /// Panics if the range is not on character boundaries or is out of bounds.
409  ///
410  /// # Examples
411  ///
412  /// ```
413  /// use smol_bytes::Utf8BytesMut;
414  ///
415  /// let buf = Utf8BytesMut::from("hello world");
416  /// let slice = buf.slice(0..5);
417  ///
418  /// assert_eq!(slice.as_str(), "hello");
419  /// ```
420  pub fn slice(&self, range: impl RangeBounds<usize>) -> Self {
421    self.try_slice(range).expect("slice failed")
422  }
423
424  /// Tries to return a slice of this buffer.
425  ///
426  /// # Errors
427  ///
428  /// Returns an error if the range is out of bounds or not on character boundaries.
429  pub fn try_slice(&self, range: impl RangeBounds<usize>) -> Result<Self, Utf8Error> {
430    <Self as Utf8Buf>::try_slice(self, range)
431  }
432}
433
434impl AsRef<str> for Utf8BytesMut {
435  fn as_ref(&self) -> &str {
436    self.as_str()
437  }
438}
439
440impl AsRef<[u8]> for Utf8BytesMut {
441  fn as_ref(&self) -> &[u8] {
442    self.as_str().as_bytes()
443  }
444}
445
446impl Borrow<str> for Utf8BytesMut {
447  fn borrow(&self) -> &str {
448    self.as_str()
449  }
450}
451
452impl core::ops::Deref for Utf8BytesMut {
453  type Target = str;
454
455  fn deref(&self) -> &Self::Target {
456    self.as_str()
457  }
458}
459
460// The trait impls hold the real bodies. Inherent methods (above) forward
461// here via explicit `<Self as Utf8Buf>::method` / `<Self as Utf8BufMut>::method`
462// UFCS so there is no ambiguity about which impl is called and no fragile
463// reliance on the inherent-over-trait method resolution rule.
464impl Utf8Buf for Utf8BytesMut {
465  fn try_split_to(&mut self, at: usize) -> Result<Self, Utf8Error> {
466    self.validate_char_boundary(at)?;
467    let inner = self
468      .inner
469      .try_split_to(at)
470      .expect("already checked bounds")
471      .unwrap_or_else(BytesMut::from);
472    Ok(Self { inner })
473  }
474
475  fn try_split_off(&mut self, at: usize) -> Result<Self, Utf8Error> {
476    self.validate_char_boundary(at)?;
477    let inner = self
478      .inner
479      .try_split_off(at)
480      .expect("already checked bounds")
481      .unwrap_or_else(BytesMut::from);
482    Ok(Self { inner })
483  }
484
485  fn try_slice(&self, range: impl RangeBounds<usize>) -> Result<Self, Utf8Error> {
486    let (start, end) = self.range_bounds_to_start_end(range)?;
487    Ok(Self {
488      inner: BytesMut::from(&self.inner.as_slice()[start..end]),
489    })
490  }
491}
492
493impl Utf8BufMut for Utf8BytesMut {
494  fn try_truncate(&mut self, new_len: usize) -> Result<(), Utf8Error> {
495    if new_len >= self.len() {
496      return Ok(());
497    }
498    if !self.as_str().is_char_boundary(new_len) {
499      return Err(Utf8Error::InvalidCharBoundary { at: new_len });
500    }
501    self.inner.truncate(new_len);
502    Ok(())
503  }
504
505  fn push(&mut self, ch: char) {
506    let mut buf = [0u8; 4];
507    let s = ch.encode_utf8(&mut buf);
508    self.inner.put_slice(s.as_bytes());
509  }
510
511  fn push_str(&mut self, s: &str) {
512    self.inner.put_slice(s.as_bytes());
513  }
514
515  fn clear(&mut self) {
516    self.inner.clear();
517  }
518}