Skip to main content

smol_bytes/bytes/strategy/
shared.rs

1//! The **Shared** strategy for `Bytes`.
2//!
3//! This module provides the [`Bytes`](crate::shared::Bytes) type alias configured with the
4//! [`Shared`](crate::shared::Shared) strategy,
5//! which prioritizes **fast conversions** and **allocation sharing** with [`bytes::Bytes`].
6//!
7//! # Key Characteristics
8//!
9//! - **Zero-copy conversions**: Converting to/from `Bytes` is O(1) for heap-allocated data
10//! - **Preserves heap allocations**: Once heap-allocated, stays on heap even when data shrinks
11//! - **Reference-counted sharing**: Heap allocations use `Arc` for cheap clones
12//! - **Recommended default**: Best for most use cases, especially I/O and networking
13//!
14//! # When to Use
15//!
16//! Choose this strategy when:
17//!
18//! - **Frequent `Bytes` conversions**: You often convert between `Bytes` and `bytes::Bytes`
19//! - **Network protocols**: Building HTTP servers, WebSocket handlers, or other I/O-heavy applications
20//! - **Performance-critical paths**: Speed is more important than memory overhead
21//! - **Shared buffers**: You frequently clone buffers and want cheap reference counting
22//!
23//! # Basic Usage
24//!
25//! ```rust
26//! use smol_bytes::shared::Bytes;
27//!
28//! // Small data (≤62 bytes) is stored inline
29//! let small = Bytes::from_static(b"hello world");
30//! assert!(!small.is_heap());
31//!
32//! // Large data is heap-allocated
33//! let large = Bytes::from(vec![1u8; 100]);
34//! assert!(large.is_heap());
35//!
36//! // Cheap clone (reference counting)
37//! let clone = large.clone();
38//! ```
39//!
40//! # Behavior Details
41//!
42//! ## Memory Layout
43//!
44//! ```text
45//! ┌─────────────────────────────────────────┐
46//! │  Bytes (64 bytes on stack)          │
47//! ├─────────────────────────────────────────┤
48//! │  Variant: Inline (≤62 bytes)            │
49//! │  ┌────────────────────────────────────┐ │
50//! │  │ [u8; 62] data                      │ │
51//! │  │ u8 length                          │ │
52//! │  │ u8 current_offset                  │ │
53//! │  └────────────────────────────────────┘ │
54//! │                                           │
55//! │  Variant: Heap (>62 bytes or shrunk)    │
56//! │  ┌────────────────────────────────────┐ │
57//! │  │ bytes::Bytes (Arc<[u8]>)           │ │
58//! │  └────────────────────────────────────┘ │
59//! └─────────────────────────────────────────┘
60//! ```
61//!
62//! ## Operations and Allocation Behavior
63//!
64//! ```rust
65//! use smol_bytes::shared::Bytes;
66//! use bytes::Buf;
67//!
68//! // Start with large heap allocation
69//! let mut data = Bytes::from(vec![1u8; 100]);
70//! assert!(data.is_heap());
71//!
72//! // After advance, still heap-allocated (Shared strategy)
73//! data.advance(70); // 30 bytes remain
74//! assert!(data.is_heap()); // ✓ Still on heap!
75//!
76//! // Zero-copy conversion to Bytes
77//! let bytes: bytes::Bytes = data.into();
78//! assert_eq!(bytes.len(), 30);
79//! ```
80//!
81//! ## Comparison: Operations That Keep vs Convert to Heap
82//!
83//! | Operation | Starting State | Result State | Notes |
84//! |-----------|---------------|--------------|-------|
85//! | `advance()` | Heap (100 bytes) | Heap (30 bytes) | Stays heap |
86//! | `truncate()` | Heap (100 bytes) | Heap (30 bytes) | Stays heap |
87//! | `split_to()` | Heap (100 bytes) | Heap (70 bytes) | Both parts may be heap |
88//! | `split_off()` | Heap (100 bytes) | Heap (30 bytes) | Both parts may be heap |
89//! | `slice()` | Heap | Heap | Non-empty slices retain shared heap backing, even if ≤62 bytes |
90//!
91//! # Performance Characteristics
92//!
93//! ## Fast Operations (O(1))
94//!
95//! - `clone()` - Reference count increment
96//! - `advance()` - Pointer adjustment
97//! - `truncate()` - Length update
98//! - `into::<Bytes>()` - Zero-copy when heap-allocated
99//!
100//! ## Linear Operations (O(62) - copies up to 62 bytes)
101//!
102//! - Creating inline values from inline sources
103//! - Operations on inline values
104//!
105//! # Examples
106//!
107//! ## Network Protocol Buffer
108//!
109//! ```rust
110//! use smol_bytes::shared::Bytes;
111//! use bytes::Buf;
112//!
113//! // Receive data from network
114//! let mut buffer = Bytes::from(vec![0u8; 1024]);
115//!
116//! // Process header (advance past it)
117//! buffer.advance(16);
118//!
119//! // Buffer stays on heap for efficient passing to bytes::Bytes
120//! assert!(buffer.is_heap());
121//!
122//! // Zero-copy conversion for writing
123//! let bytes: bytes::Bytes = buffer.into();
124//! // ... write bytes to socket
125//! ```
126//!
127//! ## Parsing with Zero-Copy Slicing
128//!
129//! ```rust
130//! use smol_bytes::shared::Bytes;
131//!
132//! let data = Bytes::from(vec![1_u8; 128]);
133//!
134//! // Extract different segments
135//! let header = data.slice(0..2);
136//! let payload = data.slice(2..8);
137//! let checksum = data.slice(8..10);
138//!
139//! // All share the same underlying allocation!
140//! ```
141//!
142//! ## Efficient Cloning
143//!
144//! ```rust
145//! use smol_bytes::shared::Bytes;
146//!
147//! let original = Bytes::from(vec![1u8; 100]);
148//!
149//! // Cheap clones (just Arc reference count)
150//! let clone1 = original.clone();
151//! let clone2 = original.clone();
152//! let clone3 = original.clone();
153//!
154//! // All share the same heap allocation
155//! assert!(original.is_heap());
156//! assert!(clone1.is_heap());
157//! ```
158
159use super::ImmutableStorage;
160use crate::{
161  buffer::{Buffer, INLINE_CAP},
162  bytes::raw::{RawBytes, Repr},
163  error::*,
164};
165use bytes::Buf;
166use core::mem;
167use core::ops::RangeBounds;
168
169/// A strategy that preserves heap allocations for fast, zero-copy conversions with [`bytes::Bytes`].
170///
171/// # Overview
172///
173/// The `Shared` strategy prioritizes **fast conversions** and **allocation sharing** with
174/// [`bytes::Bytes`]. When data is heap-allocated, it remains on the heap even after operations
175/// like `advance()`, `truncate()`, or `split_to/off()` that reduce the size below the inline
176/// capacity.
177///
178/// This makes `Shared` ideal when:
179/// - You frequently convert between `Bytes` and `Bytes`
180/// - You want to share heap allocations (cheap clones via reference counting)
181/// - Performance is more important than memory overhead
182///
183/// # Behavior
184///
185/// - **Inline → Inline**: Inline source slices stay inline
186/// - **Heap → Heap**: Every non-empty heap source slice stays shared and heap-allocated,
187///   even at or below the inline capacity; empty slices use the canonical empty value
188/// - **Conversions**: Zero-cost `From`/`Into` with `Bytes` when heap-allocated
189///
190/// ## Example
191///
192/// ```rust
193/// use smol_bytes::shared::Bytes;
194/// use bytes::Buf;
195///
196/// // Create heap-allocated bytes (>62 bytes)
197/// let mut data = Bytes::from(vec![1u8; 100]);
198/// assert!(data.is_heap());
199///
200/// // Advance past most data
201/// data.advance(70); // Only 30 bytes remain
202///
203/// // Still heap-allocated! (Shared strategy preserves heap)
204/// assert!(data.is_heap());
205///
206/// // Fast, zero-copy conversion to Bytes
207/// let bytes: bytes::Bytes = data.into();
208/// assert_eq!(bytes.len(), 30);
209/// ```
210///
211/// # Comparison with Compact
212///
213/// | Operation | Shared | Compact |
214/// |-----------|--------|---------|
215/// | Heap→Inline on shrink | ❌ No | ✅ Yes |
216/// | Bytes conversion | ⚡ Zero-copy | 📋 May copy |
217/// | Memory usage | 💾 Higher | 💾 Lower |
218/// | Best for | Speed, Bytes interop | Memory efficiency |
219#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
220pub struct Shared(());
221
222impl RawBytes<Shared> {
223  /// Create [`Bytes`] with a buffer whose lifetime is controlled
224  /// via an explicit owner.
225  ///
226  /// A common use case is to zero-copy construct from mapped memory.
227  ///
228  /// ```
229  /// # struct File;
230  /// #
231  /// # impl File {
232  /// #     pub fn open(_: &str) -> Result<Self, ()> {
233  /// #         Ok(Self)
234  /// #     }
235  /// # }
236  /// #
237  /// # mod memmap2 {
238  /// #     pub struct Mmap;
239  /// #
240  /// #     impl Mmap {
241  /// #         pub unsafe fn map(_file: &super::File) -> Result<Self, ()> {
242  /// #             Ok(Self)
243  /// #         }
244  /// #     }
245  /// #
246  /// #     impl AsRef<[u8]> for Mmap {
247  /// #         fn as_ref(&self) -> &[u8] {
248  /// #             b"buf"
249  /// #         }
250  /// #     }
251  /// # }
252  /// use smol_bytes::shared::Bytes;
253  /// use memmap2::Mmap;
254  ///
255  /// # fn main() -> Result<(), ()> {
256  /// let file = File::open("upload_bundle.tar.gz")?;
257  /// let mmap = unsafe { Mmap::map(&file) }?;
258  /// let b = Bytes::from_owner(mmap);
259  /// # Ok(())
260  /// # }
261  /// ```
262  ///
263  /// The `owner` will be transferred to the constructed [`Bytes`] object, which
264  /// will ensure it is dropped once all remaining clones of the constructed
265  /// object are dropped. The owner will then be responsible for dropping the
266  /// specified region of memory as part of its [Drop] implementation.
267  ///
268  /// Note that converting [`Bytes`] constructed from an owner into a [`BytesMut`]
269  /// will always create a deep copy of the buffer into newly allocated memory.
270  pub fn from_owner<T>(owner: T) -> Self
271  where
272    T: AsRef<[u8]> + Send + 'static,
273  {
274    Self::heap(bytes::Bytes::from_owner(owner))
275  }
276}
277
278/// Zero-copy conversion from [`bytes::Bytes`]: the heap allocation is retained
279/// and shared, the direction promised by this module's docs.
280impl From<bytes::Bytes> for RawBytes<Shared> {
281  fn from(bytes: bytes::Bytes) -> Self {
282    Self::heap(bytes)
283  }
284}
285
286impl ImmutableStorage for RawBytes<Shared> {
287  fn slice(&self, range: impl RangeBounds<usize>) -> Self {
288    self.try_slice(range).unwrap_or_else(|e| panic!("{e}"))
289  }
290
291  fn try_slice(&self, range: impl RangeBounds<usize>) -> Result<Self, crate::RangeOutOfBounds>
292  where
293    Self: Sized,
294  {
295    match &self.repr {
296      Repr::Inline(storage) => storage.try_slice(range).map(Self::inline),
297      Repr::Heap(bytes) => {
298        let len = bytes.len();
299        let (begin, end) = normalize_range(range, len)?;
300
301        if begin == end {
302          return Ok(Self::new());
303        }
304
305        Ok(Self::heap(bytes.slice(begin..end)))
306      }
307    }
308  }
309
310  fn split_to(&mut self, at: usize) -> Self {
311    let len = self.len();
312    if at == len {
313      return mem::take(self);
314    }
315    if at == 0 {
316      return Self::new();
317    }
318    assert!(at <= len, "split_to out of bounds: {:?} <= {:?}", at, len);
319
320    match &mut self.repr {
321      Repr::Inline(storage) => {
322        Self::inline(storage.try_split_to(at).expect("already checked bounds"))
323      }
324      Repr::Heap(bytes) => {
325        if at <= INLINE_CAP {
326          // SAFETY: at <= INLINE_CAP, checked above.
327          let ret = Self::inline(unsafe { Buffer::copy_from_slice(&bytes[..at]) });
328          bytes.advance(at);
329          ret
330        } else {
331          Self::heap(bytes.split_to(at))
332        }
333      }
334    }
335  }
336
337  fn split_off(&mut self, at: usize) -> Self {
338    let len = self.len();
339    if at == len {
340      return Self::new();
341    }
342    if at == 0 {
343      return mem::take(self);
344    }
345    assert!(at <= len, "split_off out of bounds: {:?} <= {:?}", at, len);
346
347    match &mut self.repr {
348      Repr::Inline(storage) => {
349        Self::inline(storage.try_split_off(at).expect("already checked bounds"))
350      }
351      Repr::Heap(bytes) => {
352        let output_size = len - at;
353        if output_size <= INLINE_CAP {
354          // SAFETY: output_size <= INLINE_CAP, checked above.
355          let ret = Self::inline(unsafe { Buffer::copy_from_slice(&bytes[at..]) });
356          bytes.truncate(at);
357          ret
358        } else {
359          Self::heap(bytes.split_off(at))
360        }
361      }
362    }
363  }
364
365  fn truncate(&mut self, new_len: usize) {
366    match &mut self.repr {
367      Repr::Inline(storage) => storage.truncate(new_len),
368      Repr::Heap(bytes) => bytes.truncate(new_len),
369    }
370  }
371
372  fn advance(&mut self, cnt: usize) {
373    match &mut self.repr {
374      Repr::Inline(storage) => storage.advance(cnt),
375      Repr::Heap(bytes) => bytes.advance(cnt),
376    }
377  }
378
379  fn copy_to_bytes(&mut self, len: usize) -> bytes::Bytes {
380    self.split_to(len).into()
381  }
382
383  fn clear(&mut self) {
384    match &mut self.repr {
385      Repr::Heap(bytes) => bytes.clear(),
386      Repr::Inline(storage) => storage.clear(),
387    }
388  }
389}
390
391#[cfg(feature = "pyo3")]
392mod python;
393#[cfg(feature = "pyo3")]
394pub use python::PySharedBytes;
395
396#[cfg(feature = "wasm")]
397mod wasm;
398
399/// A space-efficient byte buffer that shares heap allocations with [`bytes::Bytes`].
400///
401/// This type alias uses the [`Shared`] strategy.
402///
403/// # When to use
404///
405/// Use `Bytes` (with `Shared` strategy) when:
406/// - You frequently convert to/from `bytes::Bytes`
407/// - You want fast, zero-copy operations
408/// - Memory overhead is acceptable for performance gains
409///
410/// For memory-constrained applications, consider [`compact::Bytes`](super::compact::Bytes) instead.
411///
412/// ## Example
413///
414/// ```rust
415/// use smol_bytes::shared::Bytes;
416///
417/// let data = Bytes::from_static(b"hello world");
418/// assert_eq!(data.as_slice(), b"hello world");
419///
420/// // Efficient conversion to Bytes
421/// let bytes: bytes::Bytes = data.into();
422/// ```
423pub type Bytes = RawBytes<Shared>;
424
425/// A shared, immutable UTF-8 string type alias using the [`Shared`] strategy.
426///
427/// This is the shared-strategy immutable UTF-8 wrapper.
428pub type Utf8Bytes = crate::utf8_bytes::Utf8Bytes<Shared>;