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