Skip to main content

Module compact

Module compact 

Source
Available on crate features 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(), and split_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 Bytes conversions: You don’t often convert to/from bytes::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

OperationBeforeAfterConversion?
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)HeapInline✅ Yes (if result ≤62)

§Performance Characteristics

§Fast Operations (O(1))

  • clone() when inline - Simple memcpy
  • advance() when staying inline or heap
  • truncate() 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 conversion
  • truncate() when triggering conversion
  • split_to() / split_off() when triggering conversion
  • into::<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.

Type Aliases§

Bytes
A memory-efficient byte buffer that aggressively inlines data to minimize heap usage.
Utf8Bytes
A compact, memory-efficient UTF-8 string type alias using the Compact strategy.