Skip to content

Commit

Permalink
v0.1.1: add inlined small chunks
Browse files Browse the repository at this point in the history
  • Loading branch information
TimLuq committed Mar 25, 2024
1 parent 55fc195 commit c8204ca
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 18 deletions.
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bytedata"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
rust-version = "1.70.0"
description = "Representation of a byte slice that is either static, borrowed, or shared."
Expand All @@ -13,15 +13,19 @@ authors = ["TimLuq"]
[dependencies]
bytes_1 = { package = "bytes", version = "^1.2", optional = true, default-features = false }
http-body_04 = { package = "http-body", version = "0.4.5", optional = true }
http-body_1 = { package = "http-body", version = "1", optional = true }
http_02 = { package = "http", version = "0.2.4", optional = true }
http_1 = { package = "http", version = "1", optional = true }
serde_1 = { package = "serde", version = "1.0.0", optional = true, default-features = false }

[features]
default = ["macros"]
default = ["macros", "chunk"]
macros = []
chunk = []
alloc = ["serde_1?/alloc"]
bytes_1_safe = ["bytes_1"]
http-body_04 = ["dep:http-body_04", "dep:http_02", "bytes_1"]
http-body_1 = ["dep:http-body_1", "dep:http_1", "bytes_1"]
std = ["alloc"]
nightly = []
read_buf = ["std"]
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ The `SharedBytes` type is then also added as one additional representation of `B
Enables runtime allocation of byte arrays on the heap.
This allows for dynamic allocation of byte arrays which are exposed as `SharedBytes` and can be wrapped as `ByteData::Shared(_)`.

### chunk

Enables runtime representation of small byte arrays inline as `ByteData::Chunk(_)`.
This allows for optimized storage of small byte arrays that are less than or equal to 12 bytes in size.

### macros

Exposes the `concat_bytes_static!` and `concat_str_static!` macros.
Expand Down Expand Up @@ -41,3 +46,8 @@ Enables integration with the `http-body` crate (version `>=0.4.5, <0.5`).
The trait `http_body::Body` is then implemented for `ByteData` and `SharedBytes` (if `alloc` feature is used).

Since `http_body::Body` is the trait reexported as `hyper::HttpBody` in the `hyper` crate, this feature by extension also enables integration with `hyper`.

### http-body_1

Enables integration with the `http-body` crate (version `>=1.0.0, <2`).
The trait `http_body::Body` is then implemented for `ByteData` and `SharedBytes` (if `alloc` feature is used).
155 changes: 155 additions & 0 deletions src/byte_chunk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use core::{ops::RangeBounds, slice::SliceIndex};

/// A chunk of bytes that is 12 bytes or less.
#[cfg_attr(docsrs, doc(cfg(feature = "chunk")))]
#[derive(Clone, Copy)]
pub struct ByteChunk {
/// The length of the chunk.
pub(crate) len: u8,
/// The data of the chunk.
pub(crate) data: [u8; Self::LEN],
}

impl ByteChunk {
/// The maximum length of a `ByteChunk`.
pub const LEN: usize = 12;

/// Create a `ByteChunk` from a slice.
pub const fn from_slice(data: &[u8]) -> Self {
let len = data.len();
core::assert!(len <= Self::LEN, "chunk data too large");
let mut chunk = ByteChunk {
len: len as u8,
data: unsafe { core::mem::zeroed() },
};
let mut i = 0;
while i < len {
chunk.data[i] = data[i];
i += 1;
}
chunk
}

/// Create a `ByteChunk` from a fixed-size array.
pub const fn from_array<const L: usize>(data: &[u8; L]) -> Self {
core::assert!(L <= 12, "chunk data too large");
let mut chunk = ByteChunk {
len: L as u8,
data: unsafe { core::mem::zeroed() },
};
let mut i = 0;
while i < L {
chunk.data[i] = data[i];
i += 1;
}
chunk
}

/// Create a `ByteChunk` from a fixed-size array.
#[inline]
pub const fn from_byte(data: u8) -> Self {
let mut chunk: ByteChunk = ByteChunk {
len: 1,
data: unsafe { core::mem::zeroed() },
};
chunk.data[0] = data;
chunk
}

/// Get the bytes of the `ByteChunk` as a slice.
#[inline]
pub const fn as_slice(&self) -> &[u8] {
let len = self.len as usize;
if len == 0 {
return &[];
}
unsafe { core::slice::from_raw_parts(&self.data[0], len) }
}

/// Get the number bytes of the `ByteChunk`.
#[inline]
pub const fn len(&self) -> usize {
self.len as usize
}

/// Check if the `ByteChunk` is empty.
#[inline]
pub const fn is_empty(&self) -> bool {
self.len == 0
}

/// Slice the `ByteChunk` in place.
fn slice(&mut self, start: usize, end: usize) {
let len = self.len as usize;
if end > len || start > end {
panic!("ByteData: range out of bounds");
}
let len = (end - start) as u8;
self.len = len;
if len != 0 && start != 0 {
let len = len as usize;
let mut i = 0usize;
while i < len {
self.data[i] = self.data[start + i];
i += 1;
}
}
}

/// Return as subslice of the `ByteChunk`.
pub fn sliced<R: RangeBounds<usize> + SliceIndex<[u8], Output = [u8]>>(
&'_ self,
range: R,
) -> Self {
let start = match range.start_bound() {
core::ops::Bound::Included(&s) => s,
core::ops::Bound::Excluded(&s) => s + 1,
core::ops::Bound::Unbounded => 0,
};
let end = match range.end_bound() {
core::ops::Bound::Included(&e) => e + 1,
core::ops::Bound::Excluded(&e) => e,
core::ops::Bound::Unbounded => self.len as usize,
};
let mut r = *self;
Self::slice(&mut r, start, end);
r
}

/// Return as subslice of the `ByteChunk`.
pub fn into_sliced<R: RangeBounds<usize> + SliceIndex<[u8], Output = [u8]>>(
mut self,
range: R,
) -> Self {
let start = match range.start_bound() {
core::ops::Bound::Included(&s) => s,
core::ops::Bound::Excluded(&s) => s + 1,
core::ops::Bound::Unbounded => 0,
};
let end = match range.end_bound() {
core::ops::Bound::Included(&e) => e + 1,
core::ops::Bound::Excluded(&e) => e,
core::ops::Bound::Unbounded => self.len as usize,
};
Self::slice(&mut self, start, end);
self
}

/// Slice the `ByteChunk` in place.
pub fn make_sliced<R: RangeBounds<usize> + SliceIndex<[u8], Output = [u8]>>(
&'_ mut self,
range: R,
) {
let start = match range.start_bound() {
core::ops::Bound::Included(&s) => s,
core::ops::Bound::Excluded(&s) => s + 1,
core::ops::Bound::Unbounded => 0,
};
let end = match range.end_bound() {
core::ops::Bound::Included(&e) => e + 1,
core::ops::Bound::Excluded(&e) => e,
core::ops::Bound::Unbounded => self.len as usize,
};
Self::slice(self, start, end);
}
}
63 changes: 62 additions & 1 deletion src/bytedata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ pub enum ByteData<'a> {
Static(&'static [u8]),
/// A borrowed byte slice.
Borrowed(&'a [u8]),
#[cfg(feature = "chunk")]
#[cfg_attr(docsrs, doc(cfg(feature = "chunk")))]
/// A chunk of bytes that is 12 bytes or less.
Chunk(crate::byte_chunk::ByteChunk),
#[cfg(feature = "alloc")]
/// A shared byte slice.
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
Expand All @@ -36,6 +40,30 @@ impl<'a> ByteData<'a> {
Self::Static(dat)
}

#[cfg(feature = "chunk")]
/// Creates a `ByteData` from a slice of bytes. The slice must be 12 bytes or less. If the slice is larger, this will panic.
#[cfg_attr(docsrs, doc(cfg(feature = "chunk")))]
#[inline]
pub const fn from_chunk_slice(dat: &[u8]) -> Self {
Self::Chunk(crate::byte_chunk::ByteChunk::from_slice(dat))
}

#[cfg(feature = "chunk")]
/// Creates a `ByteData` from a single byte.
#[cfg_attr(docsrs, doc(cfg(feature = "chunk")))]
#[inline]
pub const fn from_byte(b0: u8) -> Self {
Self::Chunk(crate::byte_chunk::ByteChunk::from_byte(b0))
}

#[cfg(feature = "chunk")]
/// Creates a `ByteData` from an array of bytes. The array must be 12 bytes or less. If the array is larger, this will panic.
#[cfg_attr(docsrs, doc(cfg(feature = "chunk")))]
#[inline]
pub const fn from_chunk<const L: usize>(dat: &[u8; L]) -> Self {
Self::Chunk(crate::byte_chunk::ByteChunk::from_array(&dat))
}

/// Creates a `ByteData` from a borrowed slice of bytes.
#[inline]
pub const fn from_borrowed(dat: &'a [u8]) -> Self {
Expand All @@ -55,6 +83,10 @@ impl<'a> ByteData<'a> {
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[inline]
pub fn from_owned(dat: Vec<u8>) -> Self {
#[cfg(feature = "chunk")]
if dat.len() <= 12 {
return Self::Chunk(crate::byte_chunk::ByteChunk::from_slice(&dat));
}
Self::Shared(dat.into())
}

Expand Down Expand Up @@ -83,6 +115,8 @@ impl<'a> ByteData<'a> {
match self {
Self::Static(dat) => dat,
Self::Borrowed(dat) => dat,
#[cfg(feature = "chunk")]
Self::Chunk(dat) => dat.as_slice(),
#[cfg(feature = "alloc")]
Self::Shared(dat) => dat.as_slice(),
}
Expand All @@ -93,6 +127,8 @@ impl<'a> ByteData<'a> {
match self {
Self::Static(dat) => dat.len(),
Self::Borrowed(dat) => dat.len(),
#[cfg(feature = "chunk")]
Self::Chunk(dat) => dat.len(),
#[cfg(feature = "alloc")]
Self::Shared(dat) => dat.len(),
}
Expand All @@ -103,6 +139,8 @@ impl<'a> ByteData<'a> {
match self {
Self::Static(dat) => dat.is_empty(),
Self::Borrowed(dat) => dat.is_empty(),
#[cfg(feature = "chunk")]
Self::Chunk(dat) => dat.is_empty(),
#[cfg(feature = "alloc")]
Self::Shared(dat) => dat.is_empty(),
}
Expand Down Expand Up @@ -140,6 +178,8 @@ impl<'a> ByteData<'a> {
match self {
Self::Static(dat) => Self::Static(&dat[range]),
Self::Borrowed(dat) => Self::Borrowed(&dat[range]),
#[cfg(feature = "chunk")]
Self::Chunk(dat) => Self::Chunk(dat.sliced(range)),
#[cfg(feature = "alloc")]
Self::Shared(dat) => Self::Shared(dat.sliced_range(range)),
}
Expand All @@ -153,6 +193,8 @@ impl<'a> ByteData<'a> {
match self {
Self::Static(dat) => Self::Static(&dat[range]),
Self::Borrowed(dat) => Self::Borrowed(&dat[range]),
#[cfg(feature = "chunk")]
Self::Chunk(dat) => Self::Chunk(dat.sliced(range)),
#[cfg(feature = "alloc")]
Self::Shared(dat) => Self::Shared(dat.into_sliced_range(range)),
}
Expand All @@ -166,6 +208,8 @@ impl<'a> ByteData<'a> {
match self {
Self::Static(dat) => *dat = &dat[range],
Self::Borrowed(dat) => *dat = &dat[range],
#[cfg(feature = "chunk")]
Self::Chunk(dat) => dat.make_sliced(range),
#[cfg(feature = "alloc")]
Self::Shared(dat) => {
dat.make_sliced_range(range);
Expand All @@ -178,8 +222,14 @@ impl<'a> ByteData<'a> {
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn into_shared<'s>(self) -> ByteData<'s> {
match self {
#[cfg(feature = "chunk")]
Self::Borrowed(dat) if dat.len() <= 12 => {
ByteData::Chunk(crate::byte_chunk::ByteChunk::from_slice(dat))
}
Self::Borrowed(dat) => ByteData::Shared(SharedBytes::from_slice(dat)),
Self::Static(dat) => ByteData::Static(dat),
#[cfg(feature = "chunk")]
Self::Chunk(dat) => ByteData::Chunk(dat),
Self::Shared(dat) => ByteData::Shared(dat),
}
}
Expand All @@ -194,9 +244,18 @@ impl<'a> ByteData<'a> {
range: R,
) -> ByteData<'s> {
match self {
Self::Borrowed(dat) => ByteData::Shared(SharedBytes::from_slice(&dat[range])),
Self::Borrowed(dat) => {
let dat = &dat[range];
#[cfg(feature = "chunk")]
if dat.len() <= 12 {
return ByteData::Chunk(crate::byte_chunk::ByteChunk::from_slice(dat));
}
ByteData::Shared(SharedBytes::from_slice(dat))
}
Self::Shared(dat) => ByteData::Shared(dat.into_sliced_range(range)),
Self::Static(dat) => ByteData::Static(&dat[range]),
#[cfg(feature = "chunk")]
Self::Chunk(dat) => ByteData::Chunk(dat.into_sliced(range)),
}
}
}
Expand All @@ -210,6 +269,8 @@ impl ByteData<'static> {
Self::Borrowed(dat) => ByteData::Static(dat),
#[cfg(feature = "alloc")]
Self::Shared(dat) => ByteData::Shared(dat),
#[cfg(feature = "chunk")]
Self::Chunk(dat) => ByteData::Chunk(dat),
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/bytes_1/bytedata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ impl From<ByteData<'_>> for bytes::Bytes {
match dat {
ByteData::Static(dat) => bytes::Bytes::from_static(dat),
ByteData::Borrowed(dat) => bytes::Bytes::copy_from_slice(dat),
#[cfg(feature = "chunk")]
ByteData::Chunk(dat) => bytes::Bytes::copy_from_slice(dat.as_slice()),
#[cfg(feature = "alloc")]
ByteData::Shared(dat) => dat.into(),
}
Expand Down
Loading

0 comments on commit c8204ca

Please sign in to comment.