Skip to main content

multi_trait/
enc_into_buffer.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Buffer-based encoding trait for zero-allocation varint encoding.
3//!
4//! This module provides the [`EncodeIntoBuffer`] trait, which allows encoding
5//! values into an existing buffer without allocating. This is useful for
6//! performance-critical code paths where allocations should be minimized.
7//!
8//! # Performance Benefits
9//!
10//! Using `EncodeIntoBuffer` instead of [`EncodeInto`](crate::EncodeInto) provides:
11//! - **Zero allocations**: Reuses existing buffer capacity
12//! - **Better cache locality**: Fewer heap operations
13//! - **Batch encoding**: Encode multiple values into one buffer
14//! - **Predictable performance**: No allocation pauses
15//!
16//! # Use Cases
17//!
18//! - Hot paths in serialization code
19//! - Encoding multiple values sequentially
20//! - Systems with allocation constraints
21//! - Performance-sensitive applications
22//!
23//! # Examples
24//!
25//! ## Single value encoding
26//!
27//! ```rust
28//! use multi_trait::EncodeIntoBuffer;
29//!
30//! let mut buffer = Vec::new();
31//! 42u8.encode_into_buffer(&mut buffer);
32//! assert_eq!(buffer, vec![42]);
33//! ```
34//!
35//! ## Sequential encoding (multiple values)
36//!
37//! ```rust
38//! use multi_trait::EncodeIntoBuffer;
39//!
40//! let mut buffer = Vec::new();
41//! 42u8.encode_into_buffer(&mut buffer);
42//! 1000u16.encode_into_buffer(&mut buffer);
43//! 100000u32.encode_into_buffer(&mut buffer);
44//!
45//! // All three values encoded in one buffer with a single allocation
46//! assert!(buffer.len() > 3);
47//! ```
48//!
49//! ## Buffer reuse
50//!
51//! ```rust
52//! use multi_trait::EncodeIntoBuffer;
53//!
54//! let mut buffer = Vec::with_capacity(100);
55//! for i in 0u8..10 {
56//!     i.encode_into_buffer(&mut buffer);
57//! }
58//! // Buffer was reused for all 10 values with no additional allocations
59//! assert_eq!(buffer.len(), 10);
60//! ```
61
62#[cfg(not(feature = "std"))]
63use alloc::vec::Vec;
64
65use unsigned_varint::encode;
66
67/// Trait for encoding values into an existing buffer.
68///
69/// This trait provides zero-allocation encoding by appending to an existing
70/// `Vec<u8>`. This is more efficient than [`EncodeInto`](crate::EncodeInto)
71/// when:
72/// - Encoding multiple values sequentially
73/// - Working in allocation-sensitive contexts
74/// - Reusing buffers across multiple operations
75///
76/// # Performance
77///
78/// This trait is optimized for minimal overhead:
79/// - No heap allocations (unless buffer needs to grow)
80/// - Direct buffer manipulation
81/// - Inline hints for hot paths
82///
83/// # Thread Safety
84///
85/// This trait is `Send + Sync` safe. All implementations are stateless and can
86/// be called concurrently from multiple threads.
87///
88/// # Examples
89///
90/// ```rust
91/// use multi_trait::EncodeIntoBuffer;
92///
93/// // Create a reusable buffer
94/// let mut buffer = Vec::with_capacity(64);
95///
96/// // Encode values without allocation
97/// 42u8.encode_into_buffer(&mut buffer);
98/// 1000u16.encode_into_buffer(&mut buffer);
99///
100/// // Buffer contains both encoded values
101/// assert!(buffer.len() >= 2);
102/// ```
103///
104/// # Implemented For
105///
106/// - `bool`: Encoded as 0 (false) or 1 (true)
107/// - `u8`, `u16`, `u32`, `u64`, `u128`: Variable-length encoding
108/// - `usize`: Platform-dependent (32-bit or 64-bit)
109pub trait EncodeIntoBuffer {
110    /// Encode this value and append it to the given buffer.
111    ///
112    /// The buffer will be extended with the varint-encoded bytes representing
113    /// this value. The buffer's existing contents are preserved.
114    ///
115    /// # Performance
116    ///
117    /// This method is optimized to minimize allocations. If the buffer has
118    /// sufficient capacity, no allocation occurs. Otherwise, the buffer will
119    /// grow according to Vec's growth strategy.
120    ///
121    /// # Examples
122    ///
123    /// ```rust
124    /// use multi_trait::EncodeIntoBuffer;
125    ///
126    /// let mut buffer = Vec::new();
127    /// 42u8.encode_into_buffer(&mut buffer);
128    /// assert_eq!(buffer, vec![42]);
129    ///
130    /// // Append another value
131    /// 100u8.encode_into_buffer(&mut buffer);
132    /// assert_eq!(buffer.len(), 2);
133    /// ```
134    fn encode_into_buffer(&self, buffer: &mut Vec<u8>);
135}
136
137/// Macro to implement `EncodeIntoBuffer` for unsigned integer types using varint encoding.
138///
139/// This macro eliminates code duplication by generating identical implementations
140/// for different numeric types. Each implementation:
141/// 1. Creates an appropriate temporary buffer for the type
142/// 2. Encodes the value into the temporary buffer
143/// 3. Finds the encoded length
144/// 4. Extends the target buffer with the encoded bytes
145///
146/// # Usage
147///
148/// ```text
149/// impl_encode_into_buffer! {
150///     u8 => u8_buffer, u8;
151///     u16 => u16_buffer, u16;
152/// }
153/// ```
154///
155/// # Hygiene
156///
157/// This macro uses fully qualified paths to ensure proper hygiene and avoid
158/// namespace collisions with user code.
159macro_rules! impl_encode_into_buffer {
160    ($($type:ty => $buffer_fn:ident, $encode_fn:ident);+ $(;)?) => {
161        $(
162            #[doc = concat!("Encode a ", stringify!($type), " and append to a buffer")]
163            impl EncodeIntoBuffer for $type {
164                #[inline]
165                fn encode_into_buffer(&self, buffer: &mut Vec<u8>) {
166                    // Create temporary buffer for this type
167                    let mut buf = encode::$buffer_fn();
168
169                    // Encode value into temporary buffer
170                    let encoded = encode::$encode_fn(*self, &mut buf);
171
172                    // Extend the target buffer with the encoded bytes
173                    buffer.extend_from_slice(encoded);
174                }
175            }
176        )+
177    };
178}
179
180/// Encode a bool and append to a buffer
181impl EncodeIntoBuffer for bool {
182    #[inline]
183    fn encode_into_buffer(&self, buffer: &mut Vec<u8>) {
184        buffer.push(u8::from(*self));
185    }
186}
187
188// Implement EncodeIntoBuffer for all unsigned integer types using the macro
189impl_encode_into_buffer! {
190    u8 => u8_buffer, u8;
191    u16 => u16_buffer, u16;
192    u32 => u32_buffer, u32;
193    u64 => u64_buffer, u64;
194    u128 => u128_buffer, u128;
195    usize => usize_buffer, usize;
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_buffer_single_value() {
204        let mut buffer = Vec::new();
205        42u8.encode_into_buffer(&mut buffer);
206        assert_eq!(buffer, vec![42]);
207    }
208
209    #[test]
210    fn test_buffer_multiple_values() {
211        let mut buffer = Vec::new();
212        42u8.encode_into_buffer(&mut buffer);
213        100u8.encode_into_buffer(&mut buffer);
214        200u8.encode_into_buffer(&mut buffer);
215        // 42 = 1 byte, 100 = 1 byte, 200 = 2 bytes (>127 requires continuation)
216        assert_eq!(buffer.len(), 4);
217    }
218
219    #[test]
220    fn test_buffer_sequential_encoding() {
221        let mut buffer = Vec::new();
222        42u8.encode_into_buffer(&mut buffer);
223        1000u16.encode_into_buffer(&mut buffer);
224        100_000_u32.encode_into_buffer(&mut buffer);
225        // Verify buffer contains all three values
226        assert!(buffer.len() > 3);
227    }
228
229    #[test]
230    fn test_buffer_preserves_existing_content() {
231        let mut buffer = vec![0xFF, 0xEE];
232        42u8.encode_into_buffer(&mut buffer);
233        assert_eq!(buffer[0], 0xFF);
234        assert_eq!(buffer[1], 0xEE);
235        assert_eq!(buffer[2], 42);
236    }
237
238    #[test]
239    fn test_buffer_bool() {
240        let mut buffer = Vec::new();
241        true.encode_into_buffer(&mut buffer);
242        false.encode_into_buffer(&mut buffer);
243        assert_eq!(buffer, vec![1, 0]);
244    }
245
246    #[test]
247    fn test_buffer_reuse() {
248        let mut buffer = Vec::with_capacity(100);
249        for i in 0u8..10 {
250            i.encode_into_buffer(&mut buffer);
251        }
252        assert_eq!(buffer.len(), 10);
253        // Verify no reallocation occurred
254        assert!(buffer.capacity() >= 100);
255    }
256
257    #[test]
258    fn test_buffer_large_values() {
259        let mut buffer = Vec::new();
260        u8::MAX.encode_into_buffer(&mut buffer);
261        u16::MAX.encode_into_buffer(&mut buffer);
262        u32::MAX.encode_into_buffer(&mut buffer);
263        // Each max value takes multiple bytes
264        assert!(buffer.len() > 3);
265    }
266
267    #[test]
268    fn test_buffer_zero_values() {
269        let mut buffer = Vec::new();
270        0u8.encode_into_buffer(&mut buffer);
271        0u16.encode_into_buffer(&mut buffer);
272        0u32.encode_into_buffer(&mut buffer);
273        // Zero values take 1 byte each
274        assert_eq!(buffer, vec![0, 0, 0]);
275    }
276}