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}