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>;