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}