Skip to content
Open
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
33 changes: 33 additions & 0 deletions Cargo.lock

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

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ bytemuck = { version = "1.14", features = [ "min_const_generics", "must_cast" ]
glam = { version = ">=0.21", default-features = false, optional = true }
rust_decimal = { version = "1.36", default-features = false, optional = true }
serde = { version = "1.0", default-features = false, features = [ "alloc" ], optional = true }
smol_str = { version = "0.3", default-features = false, optional = true }
time = { version = "0.3", default-features = false, optional = true }
uuid = { version = "1.10", default-features = false, optional = true }

Expand All @@ -41,11 +42,11 @@ zstd = "0.13.0"

[features]
derive = [ "dep:bitcode_derive" ]
std = [ "serde?/std", "glam?/std", "arrayvec?/std" ]
std = [ "serde?/std", "glam?/std", "arrayvec?/std", "smol_str?/std" ]
default = [ "derive", "std" ]

[package.metadata.docs.rs]
features = [ "derive", "serde", "std" ]
features = [ "derive", "serde", "smol_str", "std" ]

# TODO halfs speed of benches_borrowed::bench_bitcode_decode
#[profile.bench]
Expand Down
3 changes: 2 additions & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ cargo-fuzz = true

[dependencies]
arrayvec = { version = "0.7", features = ["serde"] }
bitcode = { path = "..", features = [ "arrayvec", "rust_decimal", "serde", "time" ] }
bitcode = { path = "..", features = [ "arrayvec", "rust_decimal", "serde", "smol_str", "time" ] }
libfuzzer-sys = "0.4"
rust_decimal = "1.36.0"
serde = { version ="1.0", features = [ "derive" ] }
smol_str = { version = "0.3", features = ["serde"] }
time = { version = "0.3", features = ["serde"]}

# Prevent this from interfering with workspaces
Expand Down
2 changes: 2 additions & 0 deletions fuzz/fuzz_targets/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::num::NonZeroU32;
use std::time::Duration;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use rust_decimal::Decimal;
use smol_str::SmolStr;

#[inline(never)]
fn test_derive<T: Debug + PartialEq + Encode + DecodeOwned>(data: &[u8]) {
Expand Down Expand Up @@ -233,5 +234,6 @@ fuzz_target!(|data: &[u8]| {
SocketAddrV6,
SocketAddr,
time::Time,
SmolStr,
);
});
2 changes: 2 additions & 0 deletions src/derive/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ macro_rules! impl_both {
impl_both!(bool, BoolEncoder, BoolDecoder);
impl_both!(f32, F32Encoder, F32Decoder);
impl_both!(String, StrEncoder, StrDecoder);
#[cfg(feature = "smol_str")]
impl_both!(smol_str::SmolStr, StrEncoder, StrDecoder);

macro_rules! impl_int {
($($t:ty),+) => {
Expand Down
72 changes: 51 additions & 21 deletions src/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::error::err;
use crate::fast::{NextUnchecked, SliceImpl};
use crate::length::LengthDecoder;
use crate::u8_char::U8Char;
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec::Vec;
use core::num::NonZeroUsize;
Expand Down Expand Up @@ -57,21 +56,37 @@ impl<'b> Encoder<&'b str> for StrEncoder {
}
}

impl Encoder<String> for StrEncoder {
#[inline(always)]
fn encode(&mut self, t: &String) {
self.encode(t.as_str());
}
macro_rules! impl_string {
($t:ty) => {
impl crate::coder::Encoder<$t> for crate::str::StrEncoder {
#[inline(always)]
fn encode(&mut self, t: &$t) {
self.encode(t.as_str());
}

#[inline(always)]
fn encode_vectored<'a>(&mut self, i: impl Iterator<Item = &'a $t> + Clone)
where
$t: 'a,
{
self.encode_vectored(i.map(|s| s.as_str()));
}
}

#[inline(always)]
fn encode_vectored<'a>(&mut self, i: impl Iterator<Item = &'a String> + Clone)
where
String: 'a,
{
self.encode_vectored(i.map(String::as_str));
}
impl<'a> crate::coder::Decoder<'a, $t> for crate::str::StrDecoder<'a> {
#[inline(always)]
fn decode(&mut self) -> $t {
let v: &str = self.decode();
<$t>::from(v)
}
}
};
}

impl_string!(String);
#[cfg(feature = "smol_str")]
impl_string!(smol_str::SmolStr);

// Doesn't use VecDecoder because can't decode &[u8].
#[derive(Default)]
pub struct StrDecoder<'a> {
Expand Down Expand Up @@ -131,14 +146,6 @@ impl<'a> Decoder<'a, &'a str> for StrDecoder<'a> {
}
}

impl<'a> Decoder<'a, String> for StrDecoder<'a> {
#[inline(always)]
fn decode(&mut self) -> String {
let v: &str = self.decode();
v.to_owned()
}
}

/// Tests 128 bytes a time instead of `<[u8]>::is_ascii` which only tests 8.
/// 390% faster on 8KB, 27% faster on 1GB (RAM bottleneck).
fn is_ascii_simd(v: &[u8]) -> bool {
Expand Down Expand Up @@ -288,3 +295,26 @@ mod tests2 {
}
crate::bench_encode_decode!(str_vec: Vec<String>);
}

#[cfg(all(test, feature = "smol_str"))]
mod smol_str_tests {
use smol_str::SmolStr;

/// Short strings stay inline after decode (no heap allocation).
#[test]
fn decoded_short_string_is_not_heap_allocated() {
let s = SmolStr::new("hello");
assert!(!s.is_heap_allocated());
let decoded: SmolStr = crate::decode(&crate::encode(&s)).unwrap();
assert!(!decoded.is_heap_allocated());
}

/// Long strings are heap-allocated after decode.
#[test]
fn decoded_long_string_is_heap_allocated() {
let s = SmolStr::new("this is a longer string that exceeds inline storage");
assert!(s.is_heap_allocated());
let decoded: SmolStr = crate::decode(&crate::encode(&s)).unwrap();
assert!(decoded.is_heap_allocated());
}
}
Loading