Optimised container of immutable owned bytes. Stores Arc<dyn AsRef<[u8]>>
, Box<[u8]>
, Box<dyn AsRef<[u8]>>
, Vec<u8>
, and [u8; N]
for small N
values in one single type (no generics) with a direct move—no additional heap allocation or copying.
All TinyBuf values are slices, so you get all of the standard slice methods directly. TinyBuf also implements:
AsRef<[u8]>
Clone
Hash
PartialEq
and Eq
PartialOrd
and Ord
serde::Serialize
and serde::Deserialize
(with optional serde
feature)TinyBuf is optimised for these scenarios:
[u8; N]
, where N <= 23
.Here are some examples to demonstrate the use case for TinyBuf. For illustration purposes, let's assume we're building an in-memory key-value store, and focus on the values.
Normally, if you wish to accept some owned bytes, you would use a Vec
:
```rust
struct MyKV {
entries: HashMap<&'static str, Vec
impl MyKV {
pub fn set(&mut self, key: &'static str, value: Vec
However, this is not optimal for callers who have their bytes in an array, boxed slice, etc. They'd have to reallocate into a new Vec
:
rust
let kv: MyKV = MyKV::new();
// Fast, direct move, no reallocation or copying.
kv.set("vec", vec![0, 4, 2]);
// Requires allocating a new Vec and copying into it.
kv.set("boxslice", Box::new(&[0, 4, 2]).to_vec());
// Requires allocating a new Vec and copying into it.
kv.set("array", [0, 4, 2].to_vec());
So the next approach might be to use generics:
```rust
struct MyKV
impl
This works if the value type is always the same. But perhaps you want to accept different types of bytes—maybe different users or subsystems use different types, or sometimes arrays are used for small data to avoid heap allocation. If so, this isn't possible using the above generics approach. Instead, another approach might be to instead use boxed traits:
```rust
struct MyKV {
entries: HashMap<&'static str, Box impl MyKV {
pub fn get(&self, key: &'static str) -> Option pub fn set(&mut self, key: &'static str, value: impl AsRef<[u8]>) {
self.entries.insert(key, Box::new(value));
}
}
``` While this approach allows storing different types of bytes without copying to a This is where TinyBuf comes in: ```rust
struct MyKV {
entries: HashMap<&'static str, TinyBuf>,
} impl MyKV {
pub fn get(&self, key: &'static str) -> Option<&TinyBuf> {
self.entries.get(key)
} pub fn set let kv: MyKV = MyKV::new();
// Fast, direct move, stored inline in TinyBuf, no heap allocation or copying of data.
kv.set("boxslice", Box::new(&[0, 4, 2]));
kv.set("static", b"my static value");
kv.set("vec", vec![0, 4, 2]);
// For small arrays: fast, direct move, stored inline in TinyBuf, no heap allocation or copying of data.
kv.set("smallarray", [0, 4, 2]);
// If an array is too big, heap allocation is required, so you must explicitly opt in.
kv.set("largearray", TinyBuf::fromslice(&[0u8; 24]));
// If you already have a Box As shown in the previous Another advantage is the ability to dynamically leverage small inline arrays when reading from a slice and wanting to convert it into an owned value: ```rust
fn readfromvmmemory(&self, start: usize, end: usize) -> Vec fn readfromvmmemory(&self, start: usize, end: usize) -> TinyBuf {
// This will only heap allocate if the length is large.
TinyBuf::fromslice(&self.mem[start..end])
}
``` Note that in both cases, the data is copied because we want to own the data, but in the second TinyBuf variant, no heap allocation occurs if the length is less than or equal to 23. This optimisation is done automatically and transparently, with no additional conditional code required. Note that these will always perform a fast move operation. If you'd like TinyBuf to try and copy the data into an inline |Source type|Notes|
|---|---|
| When you call Vec
, the boxing essentially reverses that optimisation, since calling Box::new
will always move it to the heap, even for Box<[u8]>
values. This may not be too bad for data already on the heap, since it's only the struct itself, not the data, that is heap allocated, but it still requires a heap allocation of a relatively small size.Providing data
MyKV
example, one benefit of TinyBuf is to be able to accept and store a lot of different owned byte slice types while providing one single non-generic type, all without heap allocation or copying.Types
From<T>
is implemented for these types for easy fast conversion.TinyBuf::Array*
variant if the length is small enough, and do a standard .into()
otherwise, use TinyBuf::copy_if_small
.[u8; N]
where N <= 23
|Stored inline, no heap allocation or copying of data.|
|Arc<dyn AsRef<[u8]> + Send + Sync + 'static>
||
|Box<dyn AsRef<[u8]> + Send + Sync + 'static>
||
|Box<[u8]>
|This is a separate variant as converting to Box<dyn AsRef<u8>>
would require further boxing.|
|&'static [u8]
||
|Vec<u8>
|This is a separate variant as into_boxed_slice
may reallocate. Capacity must be less than 2^56
.|Cloning
TinyVec::clone
, it will try to perform optimal copying:
TinyBuf::Array*
variant, even if it's currently not a TinyBuf::Array*
.TinyBuf::Arc
, it will be cheaply cloned using Arc::clone
.TinyBuf::BoxSlice
using boxing (i.e. heap allocation).