diff --git a/Cargo.toml b/Cargo.toml index d8f19e8..abe89f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,10 @@ autocfg = "1" [dependencies] serde = { version = "1.0.95", optional = true, default-features = false, features = ["alloc"] } +bincode = { version = "2.0.0-rc", optional = true, default-features = false } [dev-dependencies] rustversion = "1" serde = { version = "1", features = ["derive"] } +bincode = { version = "2.0.0-rc" } serde_test = "1" diff --git a/src/bincode.rs b/src/bincode.rs new file mode 100644 index 0000000..55e1db2 --- /dev/null +++ b/src/bincode.rs @@ -0,0 +1,58 @@ +use super::{builder::Builder, Slab}; +use bincode::de::{BorrowDecoder, Decoder}; +use bincode::enc::Encoder; +use bincode::error::{DecodeError, EncodeError}; +use bincode::{BorrowDecode, Decode, Encode}; + +// Since Slab is stored as a map of usize to T, its implementations are quite similar to those of HashMap: +// https://github.com/bincode-org/bincode/blob/v2.0.0-rc.2/src/features/impl_std.rs#L409-L469. + +impl Encode for Slab { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + // https://docs.rs/bincode/2.0.0-rc.2/bincode/config/struct.Configuration.html#method.with_fixed_int_encoding + // With fixed integer encoding, usize is always encoded as a u64, + // so there's no need to worry about varying sizes of usize. + self.len().encode(encoder)?; + for (key, value) in self { + key.encode(encoder)?; + value.encode(encoder)?; + } + Ok(()) + } +} + +impl Decode for Slab { + fn decode(decoder: &mut D) -> Result { + let len = usize::decode(decoder)?; + decoder.claim_container_read::<(usize, T)>(len)?; + + let mut builder = Builder::with_capacity(len); + for _ in 0..len { + // See the documentation on `unclaim_bytes_read` as to why we're doing this here + decoder.unclaim_bytes_read(core::mem::size_of::<(usize, T)>()); + + let key = usize::decode(decoder)?; + let value = T::decode(decoder)?; + builder.pair(key, value); + } + Ok(builder.build()) + } +} + +impl<'de, T: BorrowDecode<'de>> BorrowDecode<'de> for Slab { + fn borrow_decode>(decoder: &mut D) -> Result { + let len = usize::decode(decoder)?; + decoder.claim_container_read::<(usize, T)>(len)?; + + let mut builder = Builder::with_capacity(len); + for _ in 0..len { + // See the documentation on `unclaim_bytes_read` as to why we're doing this here + decoder.unclaim_bytes_read(core::mem::size_of::<(usize, T)>()); + + let key = usize::borrow_decode(decoder)?; + let value = T::borrow_decode(decoder)?; + builder.pair(key, value); + } + Ok(builder.build()) + } +} diff --git a/src/lib.rs b/src/lib.rs index ec21147..2fd7970 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,6 +118,9 @@ extern crate std as alloc; #[cfg(feature = "serde")] mod serde; +#[cfg(feature = "bincode")] +mod bincode; + mod builder; use alloc::vec::{self, Vec}; diff --git a/tests/bincode.rs b/tests/bincode.rs new file mode 100644 index 0000000..dc36acb --- /dev/null +++ b/tests/bincode.rs @@ -0,0 +1,35 @@ +#![cfg(feature = "bincode")] +#![warn(rust_2018_idioms)] + +mod partial_eq; + +use bincode::{Decode, Encode}; +use partial_eq::SlabPartialEq; +use slab::Slab; +use std::fmt::Debug; + +fn assert_bytes(val: &T, bytes: &[u8]) { + let config = bincode::config::standard(); + + let encoded = bincode::encode_to_vec(val, config).unwrap(); + assert_eq!(encoded, bytes); + + let (decoded, decoded_len) = bincode::decode_from_slice::(bytes, config).unwrap(); + assert_eq!(decoded_len, bytes.len()); + assert_eq!(&decoded, val); +} + +#[test] +fn test_bincode_empty() { + let slab = Slab::::new(); + assert_bytes(&SlabPartialEq(slab), &[0]); +} + +#[test] +fn test_bincode() { + let slab = [(1, 2), (3, 4), (5, 6)] + .iter() + .copied() + .collect::>(); + assert_bytes(&SlabPartialEq(slab), &[3, 1, 4, 3, 8, 5, 12]); +} diff --git a/tests/partial_eq.rs b/tests/partial_eq.rs new file mode 100644 index 0000000..121a034 --- /dev/null +++ b/tests/partial_eq.rs @@ -0,0 +1,19 @@ +use slab::Slab; + +#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +// bincode struct is transparent by design +#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] +pub struct SlabPartialEq(pub Slab); + +impl PartialEq for SlabPartialEq { + fn eq(&self, other: &Self) -> bool { + self.0.len() == other.0.len() + && self + .0 + .iter() + .zip(other.0.iter()) + .all(|(this, other)| this.0 == other.0 && this.1 == other.1) + } +} diff --git a/tests/serde.rs b/tests/serde.rs index 1d4a204..dac0d26 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -1,25 +1,12 @@ #![cfg(feature = "serde")] #![warn(rust_2018_idioms)] -use serde::{Deserialize, Serialize}; +mod partial_eq; + +use partial_eq::SlabPartialEq; use serde_test::{assert_tokens, Token}; use slab::Slab; -#[derive(Debug, Serialize, Deserialize)] -#[serde(transparent)] -struct SlabPartialEq(Slab); - -impl PartialEq for SlabPartialEq { - fn eq(&self, other: &Self) -> bool { - self.0.len() == other.0.len() - && self - .0 - .iter() - .zip(other.0.iter()) - .all(|(this, other)| this.0 == other.0 && this.1 == other.1) - } -} - #[test] fn test_serde_empty() { let slab = Slab::::new();