alloc or std only.Expand description
The Compact strategy: Aggressively inlines data to minimize memory usage.
This strategy automatically converts heap-allocated buffers to inline storage whenever the data shrinks to fit within the inline capacity (62 bytes or less).
See compact::Bytes for usage examples and detailed documentation.
The Compact strategy for Bytes.
This module provides the Bytes type alias configured with the
Compact strategy,
which prioritizes memory efficiency by aggressively converting heap allocations back
to inline storage whenever possible.
§Key Characteristics
- Aggressive inlining: Automatically converts heap→inline when data fits (≤62 bytes)
- Memory-efficient: Minimizes heap allocations and memory overhead
- Smart optimization: Operations like
advance(),truncate(), andsplit_to/off()trigger conversions - Best for constrained environments: Ideal for embedded systems and memory-critical applications
§When to Use
Choose this strategy when:
- Memory is limited: Embedded systems, microcontrollers, or memory-constrained environments
- Many small buffers: You work with numerous buffers that frequently shrink over time
- Rare
Bytesconversions: You don’t often convert to/frombytes::Bytes - Allocation minimization: You want to minimize heap allocations at the cost of occasional copies
§Basic Usage
use smol_bytes::{compact::Bytes, Buf};
// Small data (≤62 bytes) is stored inline
let small = Bytes::from_static(b"hello world");
assert!(!small.is_heap());
// Large data starts on heap
let mut large = Bytes::from(vec![1u8; 100]);
assert!(large.is_heap());
// After shrinking, automatically converts to inline!
large.advance(70); // 30 bytes remain
assert!(!large.is_heap()); // ✓ Now inline!§Behavior Details
§Memory Layout (Same as Shared)
┌─────────────────────────────────────────┐
│ Bytes (64 bytes on stack) │
├─────────────────────────────────────────┤
│ Variant: Inline (≤62 bytes) │
│ ┌────────────────────────────────────┐ │
│ │ [u8; 62] data │ │
│ │ u8 length │ │
│ │ u8 current_offset │ │
│ └────────────────────────────────────┘ │
│ │
│ Variant: Heap (>62 bytes only) │
│ ┌────────────────────────────────────┐ │
│ │ bytes::Bytes (Arc<[u8]>) │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────┘§Operations and Allocation Behavior
use smol_bytes::{compact::Bytes, Buf};
// Start with large heap allocation
let mut data = Bytes::from(vec![1u8; 100]);
assert!(data.is_heap());
// After advance, automatically inlined (Compact strategy)
data.advance(70); // 30 bytes remain
assert!(!data.is_heap()); // ✓ Converted to inline!§Comparison: Heap→Inline Conversion Triggers
| Operation | Before | After | Conversion? |
|---|---|---|---|
advance(n) | Heap (100 bytes) | Inline (30 bytes) | ✅ Yes (if ≤62 bytes remain) |
truncate(n) | Heap (100 bytes) | Inline (30 bytes) | ✅ Yes (if n ≤62) |
split_to(n) | Heap (100 bytes) | Inline (remaining) | ✅ Yes (if remaining ≤62) |
split_off(n) | Heap (100 bytes) | Inline (first part) | ✅ Yes (if first ≤62) |
slice(range) | Heap | Inline | ✅ Yes (if result ≤62) |
§Performance Characteristics
§Fast Operations (O(1))
clone()when inline - Simple memcpyadvance()when staying inline or heaptruncate()when staying inline or heap- Operations that don’t trigger conversion
§Linear Operations (O(62) - copies up to 62 bytes)
- Heap→Inline conversion - Copies data to stack (up to 62 bytes)
advance()when triggering conversiontruncate()when triggering conversionsplit_to()/split_off()when triggering conversioninto::<Bytes>()when inline (must copy to heap)
Note: Since the maximum copy size is fixed at 62 bytes, these operations are very fast in practice!
§Examples
§Stream Processing with Automatic Inlining
use smol_bytes::{compact::Bytes, Buf};
// Process incoming stream
let mut buffer = Bytes::from(vec![0u8; 1024]);
assert!(buffer.is_heap());
// As we consume data, it automatically inlines
buffer.advance(1000); // 24 bytes remain
assert!(!buffer.is_heap()); // Saved memory!§Memory-Efficient Buffer Pool
use smol_bytes::compact::Bytes;
struct BufferPool {
buffers: Vec<Bytes>,
}
impl BufferPool {
fn new() -> Self {
Self { buffers: Vec::new() }
}
fn add(&mut self, data: Vec<u8>) {
// Automatically inlines if small enough
self.buffers.push(Bytes::from(data));
}
fn total_heap_allocations(&self) -> usize {
self.buffers.iter()
.filter(|b| b.is_heap())
.count()
}
}
let mut pool = BufferPool::new();
// Add mix of small and large buffers
pool.add(vec![1; 10]); // Inline
pool.add(vec![2; 30]); // Inline
pool.add(vec![3; 100]); // Heap
// Only one heap allocation!
assert_eq!(pool.total_heap_allocations(), 1);§Truncate for Memory Savings
use smol_bytes::compact::Bytes;
let mut data = Bytes::from(vec![1u8; 100]);
assert!(data.is_heap());
// Truncate to small size - automatically inlines
data.truncate(20);
assert!(!data.is_heap());
assert_eq!(data.len(), 20);§Smart Split Operations
use smol_bytes::compact::Bytes;
let mut data = Bytes::from(vec![1u8; 100]);
// Split off small portion - both parts optimize
let first = data.split_to(30); // first: 30 bytes (inline)
// data: 70 bytes (heap)
assert!(!first.is_heap()); // Automatically inlined!
assert!(data.is_heap()); // Still too large for inline§Trade-offs vs Shared ImmutableStorage
§Advantages
- ✅ Lower memory usage: Fewer heap allocations
- ✅ Better cache locality: More data on stack
- ✅ Fewer allocations: Automatic heap→inline conversion
- ✅ Simpler deallocation: Inline data needs no cleanup
§Disadvantages
- ❌ Conversion overhead: O(62) copy when heap→inline (up to 62 bytes)
- ❌ Bytes conversion cost: Must copy when inline
- ❌ More copies: Cloning inline data copies all bytes
- ❌ No zero-copy for small data: Inline can’t share with
Bytes
§Benchmarks
Typical performance characteristics (on x86_64):
- Heap→Inline conversion: ~10-20ns for 62 bytes
- Inline clone: ~5-10ns for 62 bytes
- Memory saved: 32 bytes per buffer (no heap overhead)
§Migration Guide
If you’re currently using shared::Bytes and considering switching:
// Before (Shared strategy)
use smol_bytes::{shared, compact, Buf};
let mut data = shared::Bytes::from(vec![1u8; 100]);
data.advance(70); // Still heap-allocated
let bytes: bytes::Bytes = data.into(); // Zero-copy ✓
// After (Compact strategy)
let mut data = compact::Bytes::from(vec![1u8; 100]);
data.advance(70); // Now inline! Saved memory ✓
let bytes: bytes::Bytes = data.into(); // Copies 30 bytes (still fast!)Rule of thumb: If you convert to Bytes more than once per buffer lifetime,
use Shared. If memory is more important than conversion speed, use Compact.
Structs§
- Compact
- A strategy that aggressively inlines data to minimize heap allocations and memory usage.