diff --git a/README.md b/README.md index 24ab790..fa49847 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,16 @@ Unless otherwise specified, all codecs support `&[u32]` only. ```text * BP32 * Copy +* FastBinaryPacking16 +* FastBinaryPacking32 * FastBinaryPacking8 * FastPFor128 (both `&[u32]` and `&[u64]`) * FastPFor256 (both `&[u32]` and `&[u64]`) -* FastBinaryPacking16 -* FastBinaryPacking32 * MaskedVByte * NewPFor * OptPFor -* PFor2008 * PFor +* PFor2008 * SimdBinaryPacking * SimdFastPFor128 * SimdFastPFor256 diff --git a/src/codec.rs b/src/codec.rs index 8bff34c..f56ebf3 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -1,6 +1,6 @@ use bytemuck::{Pod, cast_slice}; -use crate::FastPForError; +use crate::FastPForResult; /// Internal default for max decompressed length. Used by trait defaults and C++ FFI. #[inline] @@ -23,13 +23,15 @@ pub(crate) fn default_max_decoded_len(compressed_words: usize) -> usize { /// /// # Implementing this trait /// -/// ```rust,ignore +/// ``` +/// # use fastpfor::{BlockCodec, FastPForResult}; +/// struct MyCodec; /// impl BlockCodec for MyCodec { /// type Block = [u32; 256]; -/// fn encode_blocks(&self, blocks: &[[u32; 256]], out: &mut Vec) -/// -> Result<(), FastPForError> { ... } -/// fn decode_blocks(&self, input: &[u32], expected_len: Option, -/// out: &mut Vec) -> Result { ... } +/// fn encode_blocks(&mut self, blocks: &[[u32; 256]], out: &mut Vec) +/// -> FastPForResult<()> { todo!() } +/// fn decode_blocks(&mut self, input: &[u32], expected_len: Option, +/// out: &mut Vec) -> FastPForResult { todo!() } /// } /// ``` pub trait BlockCodec { @@ -54,11 +56,7 @@ pub trait BlockCodec { /// /// No remainder is possible — the caller must split the input first using /// [`slice_to_blocks`] and handle any remainder separately. - fn encode_blocks( - &mut self, - blocks: &[Self::Block], - out: &mut Vec, - ) -> Result<(), FastPForError>; + fn encode_blocks(&mut self, blocks: &[Self::Block], out: &mut Vec) -> FastPForResult<()>; /// Decompress blocks from `input`, using the length stored in the header. /// @@ -75,7 +73,7 @@ pub trait BlockCodec { input: &[u32], expected_len: Option, out: &mut Vec, - ) -> Result; + ) -> FastPForResult; /// Maximum decompressed element count for a given compressed input length. /// Reject `expected_len` values exceeding this to avoid allocation from bad data. @@ -100,9 +98,9 @@ pub trait BlockCodec { #[cfg(feature = "cpp")] pub trait BlockCodec64 { /// Compress 64-bit integers into a 32-bit word stream. - fn encode64(&mut self, input: &[u64], out: &mut Vec) -> Result<(), FastPForError>; + fn encode64(&mut self, input: &[u64], out: &mut Vec) -> FastPForResult<()>; /// Decompress 64-bit integers from a 32-bit word stream. - fn decode64(&mut self, input: &[u32], out: &mut Vec) -> Result<(), FastPForError>; + fn decode64(&mut self, input: &[u32], out: &mut Vec) -> FastPForResult<()>; } /// Compresses and decompresses an arbitrary-length `&[u32]` slice. @@ -113,7 +111,7 @@ pub trait BlockCodec64 { /// to produce an `AnyLenCodec`. pub trait AnyLenCodec { /// Compress an arbitrary-length slice of `u32` values. - fn encode(&mut self, input: &[u32], out: &mut Vec) -> Result<(), FastPForError>; + fn encode(&mut self, input: &[u32], out: &mut Vec) -> FastPForResult<()>; /// Maximum decompressed element count for a given compressed input length. /// Reject `expected_len` values exceeding this to avoid allocation from bad data. @@ -140,7 +138,7 @@ pub trait AnyLenCodec { input: &[u32], out: &mut Vec, expected_len: Option, - ) -> Result<(), FastPForError>; + ) -> FastPForResult<()>; } /// Split a flat `&[u32]` into `(&[Blocks::Block], &[u32])` without copying. @@ -154,7 +152,8 @@ pub trait AnyLenCodec { /// /// # Example /// -/// ```rust,ignore +/// ```ignore +/// # use fastpfor::{slice_to_blocks, FastPForBlock256}; /// let data: Vec = (0..600).collect(); // 2 × 256 + 88 remainder /// let (blocks, remainder) = slice_to_blocks::(&data); /// assert_eq!(blocks.len(), 2); // 2 blocks of [u32; 256] diff --git a/src/cpp/codecs.rs b/src/cpp/codecs.rs index 2ac6750..2def24a 100644 --- a/src/cpp/codecs.rs +++ b/src/cpp/codecs.rs @@ -1,6 +1,6 @@ use cxx::UniquePtr; -use crate::FastPForError; +use crate::FastPForResult; use crate::codec::{AnyLenCodec, BlockCodec64}; use crate::cpp::ffi; use crate::cpp::wrappers::{ @@ -37,7 +37,7 @@ macro_rules! implement_cpp_codecs { } impl AnyLenCodec for $name { - fn encode(&mut self, input: &[u32], out: &mut Vec) -> Result<(), FastPForError> { + fn encode(&mut self, input: &[u32], out: &mut Vec) -> FastPForResult<()> { encode32_to_vec_ffi(&self.0, input, out) } @@ -46,7 +46,7 @@ macro_rules! implement_cpp_codecs { input: &[u32], out: &mut Vec, expected_len: Option, - ) -> Result<(), FastPForError> { + ) -> FastPForResult<()> { decode32_anylen_ffi(&self.0, input, out, expected_len) } } @@ -161,10 +161,10 @@ macro_rules! implement_cpp_codecs_64 { ($($name:ident => $ffi:ident ,)*) => { $( impl BlockCodec64 for $name { - fn encode64(&mut self, input: &[u64], out: &mut Vec) -> Result<(), FastPForError> { + fn encode64(&mut self, input: &[u64], out: &mut Vec) -> FastPForResult<()> { encode64_to_vec_ffi(&self.0, input, out) } - fn decode64(&mut self, input: &[u32], out: &mut Vec) -> Result<(), FastPForError> { + fn decode64(&mut self, input: &[u32], out: &mut Vec) -> FastPForResult<()> { decode64_to_vec_ffi(&self.0, input, out) } } @@ -192,7 +192,7 @@ pub(crate) mod tests { } /// C++ `fastpfor256_codec` returns `CompositeCodec, VariableByte>` — already - /// any-length. Use it directly; do not wrap in Rust `CppComposite`. + /// any-length. Use it directly; do not wrap in Rust `CompositeCodec`. #[test] fn test_cpp_fastpfor256_composite_anylen() { let mut codec = CppFastPFor256::new(); diff --git a/src/cpp/wrappers.rs b/src/cpp/wrappers.rs index 7d600a3..90a58ad 100644 --- a/src/cpp/wrappers.rs +++ b/src/cpp/wrappers.rs @@ -1,6 +1,6 @@ use cxx::UniquePtr; -use crate::FastPForError; +use crate::FastPForResult; use crate::codec::default_max_decoded_len; use crate::cpp::ffi; use crate::helpers::AsUsize; @@ -14,7 +14,7 @@ pub fn encode32_to_vec_ffi( codec: &UniquePtr, input: &[u32], out: &mut Vec, -) -> Result<(), FastPForError> { +) -> FastPForResult<()> { let capacity = input.len() * 2 + 1024; let start = out.len(); out.resize(start + capacity, 0); @@ -28,7 +28,7 @@ fn decode32_to_vec_ffi( input: &[u32], out: &mut Vec, capacity: usize, -) -> Result<(), FastPForError> { +) -> FastPForResult<()> { if !input.is_empty() { let start = out.len(); out.resize(start + capacity, 0); @@ -43,7 +43,7 @@ pub fn decode32_anylen_ffi( input: &[u32], out: &mut Vec, expected_len: Option, -) -> Result<(), FastPForError> { +) -> FastPForResult<()> { let max = default_max_decoded_len(input.len()); let capacity = if let Some(n) = expected_len { n.is_valid_expected(max)? @@ -64,7 +64,7 @@ pub fn encode64_to_vec_ffi( codec: &UniquePtr, input: &[u64], out: &mut Vec, -) -> Result<(), FastPForError> { +) -> FastPForResult<()> { let capacity = input.len() * 3 + 1024; let start = out.len(); out.resize(start + capacity, 0); @@ -77,7 +77,7 @@ pub fn decode64_to_vec_ffi( codec: &UniquePtr, input: &[u32], out: &mut Vec, -) -> Result<(), FastPForError> { +) -> FastPForResult<()> { if !input.is_empty() { // C++ decodeArray needs output buffer. Variable-byte can pack multiple values per word. let capacity = input.len().saturating_mul(4); diff --git a/src/helpers.rs b/src/helpers.rs index 8db9446..3c2a1d1 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,4 +1,4 @@ -use crate::FastPForError; +use crate::{FastPForError, FastPForResult}; /// Finds the greatest multiple of `factor` that is less than or equal to `value`. #[cfg_attr(feature = "cpp", allow(dead_code))] @@ -18,7 +18,7 @@ pub trait AsUsize: Eq + Copy { #[inline] #[cfg(feature = "cpp")] - fn is_decoded_mismatch(self, expected: impl AsUsize) -> Result<(), FastPForError> { + fn is_decoded_mismatch(self, expected: impl AsUsize) -> FastPForResult<()> { let actual = self.as_usize(); let expected = expected.as_usize(); if self.as_usize() == expected { @@ -31,7 +31,7 @@ pub trait AsUsize: Eq + Copy { /// Returns an error if `expected` exceeds `max`. #[inline] #[cfg(feature = "cpp")] - fn is_valid_expected(self, max: impl AsUsize) -> Result { + fn is_valid_expected(self, max: impl AsUsize) -> FastPForResult { let expected = self.as_usize(); let max = max.as_usize(); if expected > max { @@ -69,12 +69,12 @@ impl AsUsize for u32 { #[cfg_attr(feature = "cpp", allow(dead_code))] pub trait GetWithErr { - fn get_val(&self, pos: impl AsUsize) -> Result; + fn get_val(&self, pos: impl AsUsize) -> FastPForResult; } impl GetWithErr for &[T] { #[inline] - fn get_val(&self, pos: impl AsUsize) -> Result { + fn get_val(&self, pos: impl AsUsize) -> FastPForResult { self.get(pos.as_usize()) .copied() .ok_or(FastPForError::NotEnoughData) @@ -83,7 +83,7 @@ impl GetWithErr for &[T] { impl GetWithErr for Vec { #[inline] - fn get_val(&self, pos: impl AsUsize) -> Result { + fn get_val(&self, pos: impl AsUsize) -> FastPForResult { self.as_slice().get_val(pos) } } diff --git a/src/lib.rs b/src/lib.rs index a0cd734..20c4948 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,18 +2,13 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] -#[cfg(not(any(feature = "cpp", feature = "rust",)))] +#[cfg(not(any(feature = "cpp", feature = "rust")))] compile_error!("At least one of the features 'cpp' or 'rust' must be enabled"); // Error types are always available regardless of which codec features are enabled. mod error; pub use error::{FastPForError, FastPForResult}; -// FIXME: need decide on the external API. Some ideas: -// - offer two sets of similar APIs - rust and cpp ffi -// - it will be possible to enable/disable each with a feature flag -// - introduce a new feature-agnostic API that will forward to either -// - if both are enabled, forward to the more stable (ffi probably) #[cfg(feature = "cpp")] /// Rust wrapper for the [`FastPFOR` C++ library](https://github.com/fast-pack/FastPFor) pub mod cpp; diff --git a/src/rust/integer_compression/fastpfor.rs b/src/rust/integer_compression/fastpfor.rs index fbf55e8..09a80b0 100644 --- a/src/rust/integer_compression/fastpfor.rs +++ b/src/rust/integer_compression/fastpfor.rs @@ -2,6 +2,7 @@ use std::array; use std::io::Cursor; use std::num::NonZeroU32; +use bytemuck::cast_slice; use bytes::{Buf as _, BufMut as _, BytesMut}; use crate::helpers::{GetWithErr, bits, greatest_multiple}; @@ -176,13 +177,13 @@ impl FastPFOR { /// - Writes header, packed data, metadata bytes, and exception values. /// /// # Arguments - /// * `thissize` - Must be multiple of `block_size` - /// * `input_offset` - Advanced by `thissize` + /// * `this_size` - Must be multiple of `block_size` + /// * `input_offset` - Advanced by `this_size` /// * `output_offset` - Advanced by compressed size fn encode_page( &mut self, input: &[u32], - thissize: u32, + this_size: u32, input_offset: &mut Cursor, output: &mut [u32], output_offset: &mut Cursor, @@ -196,7 +197,7 @@ impl FastPFOR { self.bytes_container.clear(); let mut tmp_input_offset = input_offset.position() as u32; - let final_input_offset = tmp_input_offset + thissize - self.block_size; + let final_input_offset = tmp_input_offset + this_size - self.block_size; while tmp_input_offset <= final_input_offset { self.best_bit_from_data(input, tmp_input_offset); self.bytes_container.put_u8(self.optimal_bits); @@ -331,9 +332,9 @@ impl FastPFOR { /// unpacks regular values per block, patches in exceptions by position. /// /// # Arguments - /// * `thissize` - Expected decompressed integer count + /// * `this_size` - Expected decompressed integer count /// * `input_offset` - Advanced by bytes read - /// * `output_offset` - Advanced by `thissize` + /// * `output_offset` - Advanced by `this_size` #[expect(clippy::too_many_lines)] fn decode_page( &mut self, @@ -341,7 +342,7 @@ impl FastPFOR { input_offset: &mut Cursor, output: &mut [u32], output_offset: &mut Cursor, - thissize: u32, + this_size: u32, ) -> FastPForResult<()> { let n = u32::try_from(input.len()) .map_err(|_| FastPForError::InvalidInputLength(input.len()))?; @@ -362,7 +363,7 @@ impl FastPFOR { // The C++ encoder uses a raw `memcpy` of bytes into the u32 output (no endian // conversion), and the decoder does a raw reinterpret_cast back -- both native byte // order. `cast_slice` is the exact Rust equivalent: a safe, zero-copy native view. - let input_bytes: &[u8] = bytemuck::cast_slice(input); + let input_bytes: &[u8] = cast_slice(input); let mut byte_pos = (inexcept as usize) .checked_mul(4) .filter(|&bp| bp <= input_bytes.len()) @@ -448,7 +449,7 @@ impl FastPFOR { let mut tmp_output_offset = output_offset.position() as u32; let mut tmp_input_offset = input_offset.position() as u32; - let run_end = thissize / self.block_size; + let run_end = this_size / self.block_size; for _ in 0..run_end { let bits = input_bytes.get_val(byte_pos)?; if bits > 32 { diff --git a/tests/decode_error_paths.rs b/tests/decode_error_paths.rs index 234556d..68c49e7 100644 --- a/tests/decode_error_paths.rs +++ b/tests/decode_error_paths.rs @@ -11,6 +11,7 @@ use std::io::Cursor; +use bytemuck::cast_slice_mut; use fastpfor::rust::{BLOCK_SIZE_128, DEFAULT_PAGE_SIZE, FastPFOR, Integer, Skippable}; // ── helpers ────────────────────────────────────────────────────────────────── @@ -229,7 +230,7 @@ fn decode_exception_partial_group_not_enough_data() { fn decode_block_b_too_large() { let (mut compressed, _) = compressed_with_exceptions(); let start = meta_byte_start(&compressed); - let bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut compressed); + let bytes: &mut [u8] = cast_slice_mut(&mut compressed); bytes[start] = 33; // overwrite best_b of block 0 assert!(try_decode(&compressed).is_err()); } @@ -289,7 +290,7 @@ fn find_exception_block(bytes: &[u8], meta_start: usize) -> Option<(usize, usize fn decode_exception_maxbits_too_large() { let (mut compressed, _) = compressed_with_exceptions(); let start = meta_byte_start(&compressed); - let bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut compressed); + let bytes: &mut [u8] = cast_slice_mut(&mut compressed); if let Some((_, _, mb_off)) = find_exception_block(bytes, start) { bytes[mb_off] = 33; } @@ -301,7 +302,7 @@ fn decode_exception_maxbits_too_large() { fn decode_exception_index_underflow() { let (mut compressed, _) = compressed_with_exceptions(); let start = meta_byte_start(&compressed); - let bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut compressed); + let bytes: &mut [u8] = cast_slice_mut(&mut compressed); if let Some((bb_off, _, mb_off)) = find_exception_block(bytes, start) { bytes[mb_off] = bytes[bb_off].saturating_sub(1); // maxbits < best_b } @@ -313,7 +314,7 @@ fn decode_exception_index_underflow() { fn decode_exception_index_zero() { let (mut compressed, _) = compressed_with_exceptions(); let start = meta_byte_start(&compressed); - let bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut compressed); + let bytes: &mut [u8] = cast_slice_mut(&mut compressed); if let Some((bb_off, _, mb_off)) = find_exception_block(bytes, start) { bytes[mb_off] = bytes[bb_off]; // maxbits == best_b → index 0 } @@ -368,7 +369,7 @@ fn decode_index1_pos_out_of_block() { buf.truncate(out_off.position() as usize); let start = meta_byte_start(&buf); - let bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut buf); + let bytes: &mut [u8] = cast_slice_mut(&mut buf); if let Some((bb_off, _, mb_off)) = find_exception_block(bytes, start) { if bytes[mb_off].wrapping_sub(bytes[bb_off]) == 1 && mb_off + 1 < bytes.len() { bytes[mb_off + 1] = 200; // position 200 >= block_size 128 @@ -432,7 +433,7 @@ fn decode_exception_pos_out_of_block() { buf.truncate(out_off.position() as usize); let start = meta_byte_start(&buf); - let bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut buf); + let bytes: &mut [u8] = cast_slice_mut(&mut buf); if let Some((bb_off, _, mb_off)) = find_exception_block(bytes, start) { if bytes[mb_off].wrapping_sub(bytes[bb_off]) > 1 && mb_off + 1 < bytes.len() { bytes[mb_off + 1] = 200; // position 200 >= block_size 128