Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 124 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ std = []
specialization = []
may_dangle = []
serde = ["dep:serde_core"]
borsh = ["dep:borsh"]
borsh-unstable__schema = ["borsh", "borsh/unstable__schema"]
internals = []

[dependencies]
bytes = { version = "1", optional = true, default-features = false }
serde_core = { version = "1.0.221", optional = true, default-features = false }
malloc_size_of = { version = "0.1.1", optional = true, default-features = false }
borsh = { version = "1", optional = true, default-features = false }

[dev-dependencies]
serde_test = "1.0"
Expand Down
71 changes: 71 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
//! When this optional dependency is enabled, `SmallVec` implements the `serde::Serialize` and
//! `serde::Deserialize` traits.
//!
//! ### `borsh`
//!
//! When this optional dependency is enabled, `SmallVec` implements the `borsh::BorshSerialize`
//! and `borsh::BorshDeserialize` traits. Enabling the additional `borsh-unstable__schema`
//! feature also implements `borsh::BorshSchema`, mirroring borsh's own `unstable__schema`
//! feature (unstable because it depends on borsh's derive machinery).
//!
//! ### `specialization`
//!
//! **This feature is unstable and requires a nightly build of the Rust toolchain.**
Expand Down Expand Up @@ -84,6 +91,13 @@ use core::ptr::copy;
use core::ptr::copy_nonoverlapping;
use core::ptr::NonNull;

#[cfg(feature = "borsh-unstable__schema")]
use borsh::BorshSchema;
#[cfg(feature = "borsh")]
use borsh::{
io::{Read, Result as BorshIoResult, Write},
BorshDeserialize, BorshSerialize,
};
#[cfg(feature = "bytes")]
use bytes::{buf::UninitSlice, BufMut};
#[cfg(feature = "malloc_size_of")]
Expand Down Expand Up @@ -2915,6 +2929,63 @@ where
}
}

#[cfg(feature = "borsh")]
#[cfg_attr(docsrs, doc(cfg(feature = "borsh")))]
impl<T, const N: usize> BorshSerialize for SmallVec<T, N>
where
T: BorshSerialize,
{
#[inline]
fn serialize<W: Write>(&self, writer: &mut W) -> BorshIoResult<()> {
self.as_slice().serialize(writer)
}
}

#[cfg(feature = "borsh")]
#[cfg_attr(docsrs, doc(cfg(feature = "borsh")))]
impl<T, const N: usize> BorshDeserialize for SmallVec<T, N>
where
T: BorshDeserialize,
{
#[inline]
fn deserialize_reader<R: Read>(reader: &mut R) -> BorshIoResult<Self> {
// Deserialize element-by-element (same wire format as Vec<T>: a u32 length
// prefix followed by that many elements) rather than building a Vec<T> and
// converting with `from_vec`, which would always keep the heap allocation -
// even a result with `len <= N` would spill. Pushing keeps it inline as long
// as it fits, matching how the `serde` Deserialize impl above behaves.
let len = u32::deserialize_reader(reader)?;
let mut result = SmallVec::new();
result.try_reserve(len as usize).map_err(|_| {
borsh::io::Error::new(borsh::io::ErrorKind::InvalidData, "allocation failure")
})?;
for _ in 0..len {
result.push(T::deserialize_reader(reader)?);
}
Ok(result)
}
}

#[cfg(feature = "borsh-unstable__schema")]
#[cfg_attr(docsrs, doc(cfg(feature = "borsh-unstable__schema")))]
impl<T, const N: usize> BorshSchema for SmallVec<T, N>
where
T: BorshSchema,
{
fn add_definitions_recursively(
definitions: &mut alloc::collections::BTreeMap<
borsh::schema::Declaration,
borsh::schema::Definition,
>,
) {
<alloc::vec::Vec<T> as BorshSchema>::add_definitions_recursively(definitions);
}

fn declaration() -> borsh::schema::Declaration {
<alloc::vec::Vec<T> as BorshSchema>::declaration()
}
}

#[cfg(feature = "malloc_size_of")]
impl<T, const N: usize> MallocShallowSizeOf for SmallVec<T, N> {
fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
Expand Down
27 changes: 27 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,33 @@ fn test_serde() {
);
}

#[cfg(feature = "borsh")]
#[test]
fn test_borsh() {
use borsh::{from_slice, to_vec};

let mut small_vec: SmallVec<i32, 2> = SmallVec::new();
small_vec.extend([1, 2, 3, 4]);

let bytes = to_vec(&small_vec).unwrap();
let decoded: SmallVec<i32, 2> = from_slice(&bytes).unwrap();
assert_eq!(small_vec, decoded);
assert!(decoded.spilled()); // 4 elements > inline capacity of 2

// Round-trip should also match a plain Vec's encoding.
let vec_bytes = to_vec(&alloc::vec![1i32, 2, 3, 4]).unwrap();
assert_eq!(bytes, vec_bytes);

// A result that fits inline should deserialize inline, not via a heap Vec
// conversion (regression test: an earlier version always spilled here).
let mut small: SmallVec<i32, 4> = SmallVec::new();
small.extend([1, 2]);
let small_bytes = to_vec(&small).unwrap();
let small_decoded: SmallVec<i32, 4> = from_slice(&small_bytes).unwrap();
assert_eq!(small, small_decoded);
assert!(!small_decoded.spilled());
}

#[test]
fn grow_to_shrink() {
let mut v: SmallVec<u8, 2> = SmallVec::new();
Expand Down