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