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 #[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}