multi_trait/enc_into_array.rs
1// SPDX-License-Identifier: Apache-2.0
2//! Stack-based encoding trait for no-allocation varint encoding.
3//!
4//! This module provides the [`EncodeIntoArray`] trait, which allows encoding
5//! values into a stack-allocated array without any heap allocation. This is
6//! essential for `no_std` environments and embedded systems.
7//!
8//! # Performance Benefits
9//!
10//! Using `EncodeIntoArray` provides:
11//! - **Zero heap allocations**: All data on stack
12//! - **Deterministic performance**: No GC or allocator overhead
13//! - **Embedded-friendly**: Works in `no_std` contexts
14//! - **Predictable memory**: Compile-time known sizes
15//!
16//! # Use Cases
17//!
18//! - Embedded systems without allocator
19//! - Real-time systems requiring deterministic performance
20//! - `no_std` environments
21//! - Memory-constrained devices
22//! - Safety-critical systems
23//!
24//! # Examples
25//!
26//! ## Basic stack encoding
27//!
28//! ```rust
29//! use multi_trait::EncodeIntoArray;
30//!
31//! let (array, len) = 42u8.encode_into_array();
32//! assert_eq!(&array[..len], &[42]);
33//! ```
34//!
35//! ## Working with larger values
36//!
37//! ```rust
38//! use multi_trait::EncodeIntoArray;
39//!
40//! let (array, len) = 1000u16.encode_into_array();
41//! // Varint encoding of 1000 takes 2 bytes
42//! assert_eq!(len, 2);
43//! assert_eq!(&array[..len], &[0xE8, 0x07]);
44//! ```
45//!
46//! ## Maximum encoded sizes
47//!
48//! Each type has a compile-time known maximum encoded size:
49//!
50//! ```rust
51//! use multi_trait::EncodeIntoArray;
52//!
53//! // u8 values fit in 2 bytes max
54//! assert_eq!(<u8 as EncodeIntoArray>::MAX_ENCODED_SIZE, 2);
55//!
56//! // u16 values fit in 3 bytes max
57//! assert_eq!(<u16 as EncodeIntoArray>::MAX_ENCODED_SIZE, 3);
58//!
59//! // u32 values fit in 5 bytes max
60//! assert_eq!(<u32 as EncodeIntoArray>::MAX_ENCODED_SIZE, 5);
61//! ```
62
63use unsigned_varint::encode;
64
65/// Maximum size needed for any varint encoding (u128 max = 19 bytes)
66pub const MAX_VARINT_SIZE: usize = 19;
67
68/// Trait for encoding values into a stack-allocated array.
69///
70/// This trait provides zero-allocation encoding by using stack-allocated arrays.
71/// All types use a fixed-size array of 19 bytes (enough for any varint), with
72/// each type documenting its actual maximum size via `MAX_ENCODED_SIZE`.
73///
74/// # Performance
75///
76/// This trait is optimized for embedded and `no_std` contexts:
77/// - No heap allocations
78/// - Compile-time sized buffers
79/// - Inline hints for hot paths
80/// - Deterministic performance
81///
82/// # Thread Safety
83///
84/// This trait is `Send + Sync` safe. All implementations are stateless and can
85/// be called concurrently from multiple threads.
86///
87/// # Maximum Encoded Sizes
88///
89/// The maximum varint-encoded size for each type is:
90/// - `bool`, `u8`: 2 bytes
91/// - `u16`: 3 bytes
92/// - `u32`: 5 bytes
93/// - `u64`: 10 bytes
94/// - `u128`: 19 bytes
95/// - `usize`: 10 bytes (64-bit) or 5 bytes (32-bit)
96///
97/// # Examples
98///
99/// ```rust
100/// use multi_trait::EncodeIntoArray;
101///
102/// // Encode a value to a stack array
103/// let (array, len) = 42u8.encode_into_array();
104/// assert_eq!(&array[..len], &[42]);
105///
106/// // Maximum size is known at compile time
107/// assert_eq!(<u32 as EncodeIntoArray>::MAX_ENCODED_SIZE, 5);
108/// ```
109///
110/// # Implemented For
111///
112/// - `bool`: Encoded as 0 (false) or 1 (true)
113/// - `u8`, `u16`, `u32`, `u64`, `u128`: Variable-length encoding
114/// - `usize`: Platform-dependent (32-bit or 64-bit)
115pub trait EncodeIntoArray {
116 /// The maximum number of bytes needed to encode any value of this type.
117 ///
118 /// This is a compile-time constant that documents the maximum size
119 /// for this specific type, though all types return a 19-byte array.
120 const MAX_ENCODED_SIZE: usize;
121
122 /// Encode this value into a stack-allocated array.
123 ///
124 /// Returns a tuple of:
125 /// - A 19-byte array containing the encoded bytes (may have unused space)
126 /// - The actual length of the encoded data (≤ `MAX_ENCODED_SIZE`)
127 ///
128 /// # Performance
129 ///
130 /// This method performs no heap allocations. All data is on the stack.
131 ///
132 /// # Examples
133 ///
134 /// ```rust
135 /// use multi_trait::EncodeIntoArray;
136 ///
137 /// let (array, len) = 42u8.encode_into_array();
138 /// assert_eq!(len, 1);
139 /// assert_eq!(&array[..len], &[42]);
140 /// ```
141 fn encode_into_array(&self) -> ([u8; MAX_VARINT_SIZE], usize);
142}
143
144/// Macro to implement `EncodeIntoArray` for unsigned integer types using varint encoding.
145///
146/// This macro eliminates code duplication by generating identical implementations
147/// for different numeric types. Each implementation:
148/// 1. Defines the maximum encoded size for the type
149/// 2. Creates an appropriately sized buffer on the stack
150/// 3. Encodes the value into the buffer
151/// 4. Returns the buffer and actual encoded length
152///
153/// # Usage
154///
155/// ```text
156/// impl_encode_into_array! {
157/// u8, 2 => u8_buffer, u8;
158/// u16, 3 => u16_buffer, u16;
159/// }
160/// ```
161///
162/// # Hygiene
163///
164/// This macro uses fully qualified paths to ensure proper hygiene and avoid
165/// namespace collisions with user code.
166macro_rules! impl_encode_into_array {
167 ($($type:ty, $max_size:expr => $buffer_fn:ident, $encode_fn:ident);+ $(;)?) => {
168 $(
169 #[doc = concat!("Encode a ", stringify!($type), " into a stack-allocated array")]
170 impl EncodeIntoArray for $type {
171 const MAX_ENCODED_SIZE: usize = $max_size;
172
173 #[inline]
174 fn encode_into_array(&self) -> ([u8; MAX_VARINT_SIZE], usize) {
175 // Create buffer on stack
176 let mut buf = encode::$buffer_fn();
177
178 // Encode value into buffer
179 encode::$encode_fn(*self, &mut buf);
180
181 // Find the length efficiently by locating the last byte marker
182 let len = buf
183 .iter()
184 .position(|&b| unsigned_varint::decode::is_last(b))
185 .map_or(buf.len(), |pos| pos + 1);
186
187 // Copy to fixed-size array
188 let mut result = [0u8; MAX_VARINT_SIZE];
189 result[..len].copy_from_slice(&buf[..len]);
190
191 (result, len)
192 }
193 }
194 )+
195 };
196}
197
198/// Encode a bool into a stack-allocated array
199impl EncodeIntoArray for bool {
200 const MAX_ENCODED_SIZE: usize = 1;
201
202 #[inline]
203 fn encode_into_array(&self) -> ([u8; MAX_VARINT_SIZE], usize) {
204 let mut result = [0u8; MAX_VARINT_SIZE];
205 result[0] = u8::from(*self);
206 (result, 1)
207 }
208}
209
210// Implement EncodeIntoArray for all unsigned integer types using the macro
211//
212// Maximum sizes are calculated as ceil(bits / 7) since varint encoding
213// uses 7 bits per byte for data:
214// - u8 (8 bits): ceil(8/7) = 2 bytes max
215// - u16 (16 bits): ceil(16/7) = 3 bytes max
216// - u32 (32 bits): ceil(32/7) = 5 bytes max
217// - u64 (64 bits): ceil(64/7) = 10 bytes max
218// - u128 (128 bits): ceil(128/7) = 19 bytes max
219impl_encode_into_array! {
220 u8, 2 => u8_buffer, u8;
221 u16, 3 => u16_buffer, u16;
222 u32, 5 => u32_buffer, u32;
223 u64, 10 => u64_buffer, u64;
224 u128, 19 => u128_buffer, u128;
225}
226
227// usize is platform-dependent: use 10 bytes (64-bit) to be safe
228impl EncodeIntoArray for usize {
229 const MAX_ENCODED_SIZE: Self = 10;
230
231 #[inline]
232 fn encode_into_array(&self) -> ([u8; MAX_VARINT_SIZE], usize) {
233 // Create buffer on stack
234 let mut buf = encode::usize_buffer();
235
236 // Encode value into buffer
237 encode::usize(*self, &mut buf);
238
239 // Find the length efficiently by locating the last byte marker
240 let len = buf
241 .iter()
242 .position(|&b| unsigned_varint::decode::is_last(b))
243 .map_or(buf.len(), |pos| pos + 1);
244
245 // Copy to fixed-size array
246 let mut result = [0u8; MAX_VARINT_SIZE];
247 result[..len].copy_from_slice(&buf[..len]);
248
249 (result, len)
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_array_single_byte() {
259 let (array, len) = 42u8.encode_into_array();
260 assert_eq!(len, 1);
261 assert_eq!(&array[..len], &[42]);
262 }
263
264 #[test]
265 fn test_array_multi_byte() {
266 // 128 requires 2 bytes in varint encoding
267 let (array, len) = 128u16.encode_into_array();
268 assert_eq!(len, 2);
269 assert_eq!(&array[..len], &[0x80, 0x01]);
270 }
271
272 #[test]
273 fn test_array_bool() {
274 let (array_true, len_true) = true.encode_into_array();
275 assert_eq!(len_true, 1);
276 assert_eq!(&array_true[..len_true], &[1]);
277
278 let (array_false, len_false) = false.encode_into_array();
279 assert_eq!(len_false, 1);
280 assert_eq!(&array_false[..len_false], &[0]);
281 }
282
283 #[test]
284 fn test_array_zero_values() {
285 let (array, len) = 0u8.encode_into_array();
286 assert_eq!(len, 1);
287 assert_eq!(&array[..len], &[0]);
288
289 let (array, len) = 0u32.encode_into_array();
290 assert_eq!(len, 1);
291 assert_eq!(&array[..len], &[0]);
292 }
293
294 #[test]
295 fn test_array_max_values() {
296 let (_array, len) = u8::MAX.encode_into_array();
297 assert!(len <= <u8 as EncodeIntoArray>::MAX_ENCODED_SIZE);
298
299 let (_array, len) = u16::MAX.encode_into_array();
300 assert!(len <= <u16 as EncodeIntoArray>::MAX_ENCODED_SIZE);
301
302 let (_array, len) = u32::MAX.encode_into_array();
303 assert!(len <= <u32 as EncodeIntoArray>::MAX_ENCODED_SIZE);
304 }
305
306 #[test]
307 fn test_array_max_sizes() {
308 // Verify maximum sizes are correct
309 assert_eq!(<u8 as EncodeIntoArray>::MAX_ENCODED_SIZE, 2);
310 assert_eq!(<u16 as EncodeIntoArray>::MAX_ENCODED_SIZE, 3);
311 assert_eq!(<u32 as EncodeIntoArray>::MAX_ENCODED_SIZE, 5);
312 assert_eq!(<u64 as EncodeIntoArray>::MAX_ENCODED_SIZE, 10);
313 assert_eq!(<u128 as EncodeIntoArray>::MAX_ENCODED_SIZE, 19);
314 assert_eq!(<usize as EncodeIntoArray>::MAX_ENCODED_SIZE, 10);
315 assert_eq!(<bool as EncodeIntoArray>::MAX_ENCODED_SIZE, 1);
316 }
317
318 #[test]
319 fn test_array_sequential_encoding() {
320 // Demonstrate that multiple values can be encoded independently
321 let (array1, len1) = 42u8.encode_into_array();
322 let (_array2, len2) = 1000u16.encode_into_array();
323 let (_array3, len3) = 100_000_u32.encode_into_array();
324
325 // Each encoding is independent
326 assert_eq!(&array1[..len1], &[42]);
327 assert_eq!(len2, 2);
328 assert!(len3 > 1);
329 }
330
331 #[test]
332 fn test_array_no_heap_allocation() {
333 // This test verifies that the array-based encoding doesn't use heap
334 // If it did use heap, this would fail in a no_std environment
335 let (_array, len) = u64::MAX.encode_into_array();
336 assert!(len <= <u64 as EncodeIntoArray>::MAX_ENCODED_SIZE);
337 assert_eq!(len, 10); // u64::MAX takes 10 bytes in varint
338 }
339
340 #[test]
341 fn test_array_deterministic() {
342 // Encoding the same value should always produce the same result
343 let (array1, len1) = 12345u32.encode_into_array();
344 let (array2, len2) = 12345u32.encode_into_array();
345
346 assert_eq!(len1, len2);
347 assert_eq!(&array1[..len1], &array2[..len2]);
348 }
349
350 #[test]
351 fn test_array_unused_space_is_zeroed() {
352 // Verify that unused space in the array is zeroed
353 let (array, len) = 42u8.encode_into_array();
354 assert_eq!(len, 1);
355 // Check that bytes beyond len are zero
356 for &byte in &array[len..MAX_VARINT_SIZE] {
357 assert_eq!(byte, 0);
358 }
359 }
360}