Skip to main content

columnar/
bytes.rs

1//! Logic related to the transformation to and from bytes.
2//!
3//! The methods here line up with the `AsBytes` and `FromBytes` traits.
4//!
5//! The encoding uses an index of byte offsets prepended to the data, enabling
6//! random access to individual byte slices and `u64`-aligned decoding.
7//!
8//! The most reliable entry point to the read side of this functionality is the `Stash` type,
9//! which can be formed from any type that implements `Deref<Target=[u8]>`. Doing so will check
10//! `u64` alignment, copy the contents if misaligned, and perform some structural validation.
11
12/// A trait for writing bytes, usable in `no_std` environments.
13///
14/// This replaces `std::io::Write` for the columnar encoding functions.
15/// Implementations exist for `Vec<u8>` (always) and `std::io::Write` (with the `std` feature).
16pub trait WriteBytes {
17    /// The error type returned by write operations.
18    type Error;
19    /// Write all bytes from the slice, or return an error.
20    fn write_all(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
21}
22
23#[cfg(feature = "std")]
24impl<W: std::io::Write> WriteBytes for W {
25    type Error = std::io::Error;
26    #[inline(always)]
27    fn write_all(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
28        std::io::Write::write_all(self, bytes)
29    }
30}
31
32#[cfg(not(feature = "std"))]
33impl WriteBytes for alloc::vec::Vec<u8> {
34    type Error = core::convert::Infallible;
35    #[inline(always)]
36    fn write_all(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
37        self.extend_from_slice(bytes);
38        Ok(())
39    }
40}
41
42
43/// A binary encoding of sequences of byte slices.
44///
45/// The encoding starts with a sequence of n+1 offsets describing where to find the n slices in the bytes that follow.
46/// Treating the offsets as a byte slice too, each offset indicates the location (in bytes) of the end of its slice.
47/// Each byte slice can be found from a pair of adjacent offsets, where the first is rounded up to a multiple of eight.
48/// This means that slices that are not multiples of eight bytes may leave unread bytes at their end, which is fine.
49pub mod indexed {
50
51    use alloc::{vec::Vec, string::String};
52    use crate::AsBytes;
53
54    /// Encoded length in number of `u64` words required.
55    pub fn length_in_words<'a, A>(item: &A) -> usize where A : AsBytes<'a> {
56        1 + (0..A::SLICE_COUNT).map(|i| { let (_, bytes) = item.get_byte_slice(i); 1 + bytes.len().div_ceil(8) }).sum::<usize>()
57    }
58    /// Encoded length in number of `u8` bytes required.
59    pub fn length_in_bytes<'a, A>(bytes: &A) -> usize where A : AsBytes<'a> { 8 * length_in_words(bytes) }
60
61    /// Encodes `item` into `u64` aligned words.
62    ///
63    /// The sequence of byte slices are appended, with padding to have each slice start `u64` aligned.
64    /// The sequence is then pre-pended with as many byte offsets as there are slices in `item`, plus one.
65    /// The byte offsets indicate where each slice ends, and by rounding up to `u64` alignemnt where the next slice begins.
66    /// The first offset indicates where the list of offsets itself ends, and where the first slice begins.
67    ///
68    /// We will need to visit `as_bytes` three times to extract this information, so the method should be efficient and inlined.
69    /// The first read writes the first offset, the second writes each other offset, and the third writes the bytes themselves.
70    ///
71    /// The offsets are zero-based, rather than based on `store.len()`.
72    /// If you call the method with a non-empty `store` be careful decoding.
73    pub fn encode<'a, A>(store: &mut Vec<u64>, item: &A)
74    where A : AsBytes<'a>,
75    {
76        let count = A::SLICE_COUNT;
77        // Pass 1: Write the first offset (end of offset table), then each slice's end position.
78        let offsets_end: u64 = TryInto::<u64>::try_into((1 + count) * core::mem::size_of::<u64>()).unwrap();
79        store.push(offsets_end);
80        let mut position_bytes = offsets_end;
81        for i in 0..count {
82            let (align, bytes) = item.get_byte_slice(i);
83            assert!(align <= 8);
84            let to_push: u64 = position_bytes + TryInto::<u64>::try_into(bytes.len()).unwrap();
85            store.push(to_push);
86            let round_len: u64 = ((bytes.len() + 7) & !7).try_into().unwrap();
87            position_bytes += round_len;
88        }
89        // Pass 2: Append each byte slice, with padding to align starts to `u64`.
90        for i in 0..count {
91            let (_align, bytes) = item.get_byte_slice(i);
92            let whole_words = 8 * (bytes.len() / 8);
93            if let Ok(words) = bytemuck::try_cast_slice(&bytes[.. whole_words]) {
94                store.extend_from_slice(words);
95            }
96            else {
97                let store_len = store.len();
98                store.resize(store_len + whole_words/8, 0);
99                let slice = bytemuck::try_cast_slice_mut(&mut store[store_len..]).expect("&[u64] should convert to &[u8]");
100                slice.copy_from_slice(&bytes[.. whole_words]);
101            }
102            let remaining_bytes = &bytes[whole_words..];
103            if !remaining_bytes.is_empty() {
104                let mut remainder = 0u64;
105                let transmute: &mut [u8] = bytemuck::try_cast_slice_mut(core::slice::from_mut(&mut remainder)).expect("&[u64] should convert to &[u8]");
106                for (i, byte) in remaining_bytes.iter().enumerate() {
107                    transmute[i] = *byte;
108                }
109                store.push(remainder);
110            }
111        }
112    }
113
114    pub fn write<'a, A, W>(writer: &mut W, item: &A) -> Result<(), W::Error>
115    where
116        A: AsBytes<'a>,
117        W: super::WriteBytes,
118    {
119        let count = A::SLICE_COUNT;
120        // Pass 1: Write the first offset (end of offset table), then each slice's end position.
121        let offsets_end: u64 = TryInto::<u64>::try_into((1 + count) * core::mem::size_of::<u64>()).unwrap();
122        writer.write_all(bytemuck::cast_slice(core::slice::from_ref(&offsets_end)))?;
123        let mut position_bytes = offsets_end;
124        for i in 0..count {
125            let (align, bytes) = item.get_byte_slice(i);
126            assert!(align <= 8);
127            let to_push: u64 = position_bytes + TryInto::<u64>::try_into(bytes.len()).unwrap();
128            writer.write_all(bytemuck::cast_slice(core::slice::from_ref(&to_push)))?;
129            let round_len: u64 = ((bytes.len() + 7) & !7).try_into().unwrap();
130            position_bytes += round_len;
131        }
132        // Pass 2: Append each byte slice, with padding to align starts to `u64`.
133        for i in 0..count {
134            let (_align, bytes) = item.get_byte_slice(i);
135            writer.write_all(bytes)?;
136            let padding = ((bytes.len() + 7) & !7) - bytes.len();
137            if padding > 0 {
138                writer.write_all(&[0u8;8][..padding])?;
139            }
140        }
141
142        Ok(())
143    }
144
145    /// Decodes an encoded sequence of byte slices. Each result will be `u64` aligned.
146    #[inline(always)]
147    pub fn decode(store: &[u64]) -> impl Iterator<Item=&[u8]> {
148        let slices = store[0] as usize / 8 - 1;
149        let index = &store[..slices + 1];
150        let last = index[slices] as usize;
151        let bytes: &[u8] = &bytemuck::cast_slice(store)[..last];
152        (0 .. slices).map(move |i| {
153            let upper = (index[i + 1] as usize).min(last);
154            let lower = (((index[i] as usize) + 7) & !7).min(upper);
155            &bytes[lower .. upper]
156        })
157    }
158
159
160    /// A zero-allocation view into indexed-encoded data, providing random access to individual slices.
161    ///
162    /// Constructed from `&[u64]` in O(1), this wraps the offset index and data region
163    /// and provides `get(k)` to retrieve the k-th slice as `(&[u64], u8)`.
164    /// Each access is independent — no iterator state — enabling LLVM to eliminate
165    /// unused field lookups entirely.
166    #[derive(Copy, Clone)]
167    pub struct DecodedStore<'a> {
168        /// The offset index: `index[0]` is the byte offset where data starts,
169        /// `index[k+1]` is the byte offset where slice k ends.
170        index: &'a [u64],
171        /// The data region, pre-sliced to include only valid words.
172        words: &'a [u64],
173    }
174
175    impl<'a> DecodedStore<'a> {
176        /// Creates a decoded view of an indexed-encoded `&[u64]` store.
177        ///
178        /// This is O(1) — it just reads the first offset to locate the index
179        /// and data regions. No allocation, no iteration.
180        #[inline(always)]
181        pub fn new(store: &'a [u64]) -> Self {
182            let slices = store.first().copied().unwrap_or(0) as usize / 8;
183            debug_assert!(slices <= store.len(), "DecodedStore::new: slice count {slices} exceeds store length {}", store.len());
184            let index = store.get(..slices).unwrap_or(&[]);
185            let last = index.last().copied().unwrap_or(0) as usize;
186            let last_w = (last + 7) / 8;
187            debug_assert!(last_w <= store.len(), "DecodedStore::new: last word offset {last_w} exceeds store length {}", store.len());
188            let words = store.get(..last_w).unwrap_or(&[]);
189            Self { index, words }
190        }
191        /// Returns the k-th slice as `(&[u64], u8)`.
192        ///
193        /// The `u8` is the number of valid trailing bytes in the last word
194        /// (0 means all 8 are valid). Returns an empty slice for out-of-bounds access.
195        #[inline(always)]
196        pub fn get(&self, k: usize) -> (&'a [u64], u8) {
197            debug_assert!(k + 1 < self.index.len(), "DecodedStore::get: index {k} out of bounds (len {})", self.index.len().saturating_sub(1));
198            let upper = (*self.index.get(k + 1).unwrap_or(&0) as usize)
199                .min(self.words.len() * 8);
200            let lower = (((*self.index.get(k).unwrap_or(&0) as usize) + 7) & !7)
201                .min(upper);
202            let upper_w = ((upper + 7) / 8).min(self.words.len());
203            let lower_w = (lower / 8).min(upper_w);
204            let tail = (upper % 8) as u8;
205            (self.words.get(lower_w..upper_w).unwrap_or(&[]), tail)
206        }
207        /// The number of slices in the store.
208        #[inline(always)]
209        pub fn len(&self) -> usize {
210            self.index.len().saturating_sub(1)
211        }
212    }
213
214    /// Validates the internal structure of indexed-encoded data.
215    ///
216    /// Checks that offsets are well-formed, in bounds, and that the slice count matches
217    /// `expected_slices`. This is a building block for [`validate`]; prefer calling
218    /// `validate` directly unless you need structural checks alone.
219    pub fn validate_structure(store: &[u64], expected_slices: usize) -> Result<(), String> {
220        if store.is_empty() {
221            return Err("store is empty".into());
222        }
223        let first = store[0] as usize;
224        if first % 8 != 0 {
225            return Err(format!("first offset {} is not a multiple of 8", first));
226        }
227        let slices = first / 8 - 1;
228        if slices + 1 > store.len() {
229            return Err(format!("index requires {} words but store has {}", slices + 1, store.len()));
230        }
231        if slices != expected_slices {
232            return Err(format!("expected {} slices but found {}", expected_slices, slices));
233        }
234        let store_bytes = store.len() * 8;
235        let mut prev_upper = first;
236        for i in 0..slices {
237            let offset = store[i + 1] as usize;
238            if offset > store_bytes {
239                return Err(format!("slice {} offset {} exceeds store size {}", i, offset, store_bytes));
240            }
241            if offset < prev_upper {
242                return Err(format!("slice {} offset {} precedes previous end {}", i, offset, prev_upper));
243            }
244            // Advance prev_upper to the aligned start of the next slice.
245            prev_upper = (offset + 7) & !7;
246        }
247        Ok(())
248    }
249
250    /// Validates that `store` contains well-formed data compatible with type `T`.
251    ///
252    /// Checks both the internal structure of the encoding (offsets, slice count) and
253    /// type-level compatibility (each slice's byte length is a multiple of its element
254    /// size). Call this once at trust boundaries when receiving encoded data.
255    ///
256    /// The `from_store` decode path performs no further validation at access time:
257    /// it will not panic on malformed data, but may return incorrect results.
258    /// There is no undefined behavior in any case. Call this method once before
259    /// using `from_store` to ensure the data is well-formed.
260    ///
261    /// ```ignore
262    /// type B<'a> = <MyContainer as Borrow>::Borrowed<'a>;
263    /// indexed::validate::<B>(&store)?;
264    /// // Now safe to use the non-panicking path:
265    /// let ds = indexed::DecodedStore::new(&store);
266    /// let borrowed = B::from_store(&ds, &mut 0);
267    /// ```
268    pub fn validate<'a, T: crate::FromBytes<'a>>(store: &[u64]) -> Result<(), String> {
269        validate_structure(store, T::SLICE_COUNT)?;
270        let ds = DecodedStore::new(store);
271        let slices: Vec<_> = (0..ds.len()).map(|i| ds.get(i)).collect();
272        T::validate(&slices)
273    }
274
275    /// Decodes a specific byte slice by index. It will be `u64` aligned.
276    #[inline(always)]
277    pub fn decode_index(store: &[u64], index: u64) -> &[u8] {
278        let index = index as usize;
279        let bytes: &[u8] = bytemuck::cast_slice(store);
280        let upper = (store[index + 1] as usize).min(bytes.len());
281        let lower = (((store[index] as usize) + 7) & !7).min(upper);
282        &bytes[lower .. upper]
283    }
284
285    #[cfg(test)]
286    mod test {
287
288        use alloc::{vec, vec::Vec, string::String};
289        use crate::{Borrow, ContainerOf};
290        use crate::common::Push;
291        use crate::AsBytes;
292
293        use super::{encode, decode};
294
295        fn assert_roundtrip<'a, AB: AsBytes<'a>>(item: &AB) {
296            let mut store = Vec::new();
297            encode(&mut store, item);
298            assert!(item.as_bytes().map(|x| x.1).eq(decode(&store)));
299        }
300
301        #[test]
302        fn round_trip() {
303
304            let mut column: ContainerOf<Result<u64, String>> = Default::default();
305            for i in 0..10000u64 {
306                column.push(&Ok::<u64, String>(i));
307                column.push(&Err::<u64, String>(format!("{:?}", i)));
308            }
309
310            assert_roundtrip(&column.borrow());
311        }
312
313        #[test]
314        fn validate_well_formed() {
315            use crate::common::Push;
316
317            let mut column: ContainerOf<(u64, u64, u64)> = Default::default();
318            for i in 0..100u64 { column.push(&(i, i+1, i+2)); }
319            let mut store = Vec::new();
320            encode(&mut store, &column.borrow());
321
322            type B<'a> = crate::BorrowedOf<'a, (u64, u64, u64)>;
323            assert!(super::validate::<B>(&store).is_ok());
324
325            // Wrong slice count should fail structural validation.
326            assert!(super::validate_structure(&store, 5).is_err());
327        }
328
329        #[test]
330        fn validate_mixed_types() {
331            use crate::common::Push;
332
333            let mut column: ContainerOf<(u64, String, Vec<u32>)> = Default::default();
334            for i in 0..50u64 {
335                column.push(&(i, format!("hello {i}"), vec![i as u32; i as usize]));
336            }
337            let mut store = Vec::new();
338            encode(&mut store, &column.borrow());
339
340            type B<'a> = crate::BorrowedOf<'a, (u64, String, Vec<u32>)>;
341            assert!(super::validate::<B>(&store).is_ok());
342        }
343
344    }
345}
346
347/// A container of either typed columns, or serialized bytes that can be borrowed as the former.
348pub mod stash {
349
350    use alloc::{vec::Vec, string::String};
351    use crate::{Len, FromBytes};
352    /// A container of either typed columns, or serialized bytes that can be borrowed as the former.
353    ///
354    /// When `B` dereferences to a byte slice, the container can be borrowed as if the container type `C`.
355    /// This container inherits the readable properties of `C` through borrowing, and the implements
356    /// writeable properties by first converting to the `Typed` variant, which is not inexpensive.
357    ///
358    /// The container can be cleared and pushed into. When cleared it reverts to a typed variant, and when
359    /// pushed into if the typed variant it will accept the item, and if not it will panic.
360    ///
361    /// The best ways to construct a `Stash` is with either the `Default` implementation to get an empty
362    /// writeable version, or with the `try_from_bytes` method that attempts to install a type that dereferences
363    /// to a byte slice in the `Bytes` variant, after validating some structural properties.
364    ///
365    /// One can form a `Stash` directly by loading the variants, which are public. Do so with care,
366    /// as loading mis-aligned `B` into the `Bytes` variant can result in a run-time panic, and
367    /// loading structurally invalid data into either the `Bytes` or `Align` variant can produce
368    /// incorrect results at runtime (clamped index accesses, for example). The validation does not
369    /// confirm that the internal structure of types are valid, for example that all vector bounds
370    /// are in-bounds for their values, and these may result in panics at runtime for invalid data.
371    #[derive(Clone)]
372    pub enum Stash<C, B> {
373        /// The typed variant of the container.
374        Typed(C),
375        /// The bytes variant of the container.
376        Bytes(B),
377        /// Relocated, aligned binary data, if `Bytes` doesn't work for some reason.
378        ///
379        /// Most commonly this works around misaligned binary data, but it can also be useful if the `B`
380        /// type is a scarce resource that should be released.
381        Align(alloc::sync::Arc<[u64]>),
382    }
383
384    impl<C: Default, B> Default for Stash<C, B> { fn default() -> Self { Self::Typed(Default::default()) } }
385
386    impl<C: core::fmt::Debug, B: core::ops::Deref<Target=[u8]>> core::fmt::Debug for Stash<C, B> {
387        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
388            match self {
389                Stash::Typed(c) => f.debug_tuple("Typed").field(c).finish(),
390                Stash::Bytes(b) => f.debug_tuple("Bytes").field(&&b[..]).finish(),
391                Stash::Align(a) => f.debug_tuple("Align").field(a).finish(),
392            }
393        }
394    }
395
396    impl<C: crate::ContainerBytes, B: core::ops::Deref<Target = [u8]>> Stash<C, B> {
397        /// An analogue of `TryFrom` for any `B: Deref<Target=[u8]>`, avoiding coherence issues.
398        ///
399        /// This is the recommended way to form a `Stash`, as it performs certain structural validation
400        /// steps that the stash will then skip in future borrowing and indexing operations. If the data
401        /// are structurally invalid, e.g. the wrong framing header, the wrong number of slices for `C`,
402        /// this will return an error. If this returns a `Stash` then all accesses that do not panic should
403        /// be correct. The resulting `Stash` may still panic if the internal structure of the data
404        /// are inconsistent, for example if any vector bounds are out-of-bounds for their values slice.
405        ///
406        /// There is no `unsafe` that is called through this type, and invalid data can result in panics
407        /// or incorrect results, but not undefined behavior.
408        ///
409        /// # Example
410        ///
411        /// ```rust
412        /// use columnar::{Columnar, Borrow, ContainerOf};
413        /// use columnar::common::{Push, Index};
414        /// use columnar::bytes::stash::Stash;
415        ///
416        /// // Build a typed container and populate it.
417        /// let mut stash: Stash<ContainerOf<(u64, String)>, Vec<u8>> = Default::default();
418        /// stash.push(&(0u64, format!("hello")));
419        /// stash.push(&(1u64, format!("world")));
420        ///
421        /// // Serialize to bytes.
422        /// let mut bytes: Vec<u8> = Vec::new();
423        /// stash.write_bytes(&mut bytes);
424        ///
425        /// // Reconstruct from bytes, with validation.
426        /// let stash: Stash<ContainerOf<(u64, String)>, Vec<u8>> =
427        ///     Stash::try_from_bytes(bytes).expect("valid data");
428        ///
429        /// // Borrow and index into individual columns.
430        /// let borrowed = stash.borrow();
431        /// assert_eq!(*Index::get(&borrowed.0, 0), 0u64);
432        /// assert_eq!(borrowed.1.get(1), b"world");
433        /// ```
434        pub fn try_from_bytes(bytes: B) -> Result<Self, String> {
435            use crate::bytes::indexed::validate;
436            use crate::Borrow;
437            if !(bytes.len() % 8 == 0) { return Err(format!("bytes.len() = {:?} not a multiple of 8", bytes.len())) }
438            if let Ok(words) = bytemuck::try_cast_slice::<_, u64>(&bytes) {
439                validate::<<C as Borrow>::Borrowed<'_>>(words)?;
440                Ok(Self::Bytes(bytes))
441            }
442            else {
443                // Re-locating bytes for alignment reasons.
444                let mut alloc: Vec<u64> = vec![0; bytes.len() / 8];
445                bytemuck::cast_slice_mut(&mut alloc[..]).copy_from_slice(&bytes[..]);
446                validate::<<C as Borrow>::Borrowed<'_>>(&alloc)?;
447                Ok(Self::Align(alloc.into()))
448            }
449        }
450    }
451
452    impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]> + Clone + 'static> crate::Borrow for Stash<C, B> {
453
454        type Ref<'a> = <C as crate::Borrow>::Ref<'a>;
455        type Borrowed<'a> = <C as crate::Borrow>::Borrowed<'a>;
456
457        #[inline(always)] fn borrow<'a>(&'a self) -> Self::Borrowed<'a> { self.borrow() }
458        #[inline(always)] fn reborrow<'b, 'a: 'b>(item: Self::Borrowed<'a>) -> Self::Borrowed<'b> where Self: 'a { <C as crate::Borrow>::reborrow(item) }
459        #[inline(always)] fn reborrow_ref<'b, 'a: 'b>(item: Self::Ref<'a>) -> Self::Ref<'b> where Self: 'a { <C as crate::Borrow>::reborrow_ref(item) }
460    }
461
462    impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]>> Len for Stash<C, B> {
463        #[inline(always)] fn len(&self) -> usize { self.borrow().len() }
464    }
465
466    impl<C: crate::Container + crate::ContainerBytes, B: core::ops::Deref<Target=[u8]>> Stash<C, B> {
467        /// Converts the stash to the `Typed` variant, by copying the borrowed data into a new container.
468        pub fn to_typed(&self) -> Self {
469            let borrowed = self.borrow();
470            let len = borrowed.len();
471            let mut container = C::with_capacity_for(core::iter::once(borrowed));
472            container.extend_from_self(borrowed, 0..len);
473            Self::Typed(container)
474        }
475        /// Converts the stash to the `Align` variant, by serializing the borrowed data into aligned words.
476        pub fn to_aligned(&self) -> Self {
477            let borrowed = self.borrow();
478            let mut store = Vec::with_capacity(crate::bytes::indexed::length_in_words(&borrowed));
479            crate::bytes::indexed::encode(&mut store, &borrowed);
480            Self::Align(store.into())
481        }
482        /// Ensures the stash is in the `Typed` variant, converting in place if needed, and returns a mutable reference.
483        pub fn make_typed(&mut self) -> &mut C {
484            if !matches!(self, Self::Typed(_)) {
485                *self = self.to_typed();
486            }
487            match self {
488                Stash::Typed(t) => t,
489                _ => unreachable!(),
490            }
491        }
492        /// Ensures the stash is in the `Align` variant, converting in place if needed, and returns a reference.
493        pub fn make_aligned(&mut self) -> &alloc::sync::Arc<[u64]> {
494            if !matches!(self, Self::Align(_)) {
495                *self = self.to_aligned();
496            }
497            match self {
498                Stash::Align(a) => a,
499                _ => unreachable!(),
500            }
501        }
502    }
503
504    impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]>> Stash<C, B> {
505        /// Borrows the contents, either from a typed container or by decoding serialized bytes.
506        ///
507        /// This method is relatively cheap but is not free.
508        #[inline(always)] pub fn borrow<'a>(&'a self) -> <C as crate::Borrow>::Borrowed<'a> {
509            match self {
510                Stash::Typed(t) => t.borrow(),
511                Stash::Bytes(b) => {
512                    let store = crate::bytes::indexed::DecodedStore::new(bytemuck::cast_slice(b));
513                    <C::Borrowed<'_> as FromBytes>::from_store(&store, &mut 0)
514                },
515                Stash::Align(a) => {
516                    let store = crate::bytes::indexed::DecodedStore::new(a);
517                    <C::Borrowed<'_> as FromBytes>::from_store(&store, &mut 0)
518                },
519            }
520        }
521        /// The number of bytes needed to write the contents using the [`indexed`] encoder.
522        ///
523        /// This may be less than the length of the contained bytes or words, if they overshoot.
524        pub fn length_in_bytes(&self) -> usize { crate::bytes::indexed::length_in_bytes(&self.borrow()) }
525        /// Write the contents into a [`WriteBytes`](crate::bytes::WriteBytes) destination.
526        pub fn write_bytes<W: crate::bytes::WriteBytes>(&self, writer: &mut W) -> Result<(), W::Error> {
527            match self {
528                Stash::Typed(t) => { crate::bytes::indexed::write(writer, &t.borrow())?; },
529                Stash::Bytes(b) => writer.write_all(&b[..])?,
530                Stash::Align(a) => writer.write_all(bytemuck::cast_slice(&a[..]))?,
531            }
532            Ok(())
533        }
534    }
535
536    // This implementation converts to owned data if it is not already, which can be expensive.
537    impl<T, C: crate::Container + crate::ContainerBytes + crate::Push<T>, B: core::ops::Deref<Target=[u8]>> crate::Push<T> for Stash<C, B> {
538        fn push(&mut self, item: T) {
539            self.make_typed();
540            match self {
541                Stash::Typed(t) => t.push(item),
542                _ => unreachable!(),
543            }
544        }
545    }
546
547    impl<C: crate::Clear + Default, B> crate::Clear for Stash<C, B> {
548        fn clear(&mut self) {
549            match self {
550                Stash::Typed(t) => t.clear(),
551                Stash::Bytes(_) | Stash::Align(_) => {
552                    *self = Stash::Typed(Default::default());
553                }
554            }
555        }
556    }
557
558    // Both methods materialize to the `Typed` variant before writing, which can be expensive when
559    // starting from `Bytes` or `Align`. This matches the behavior of the `Push` implementation.
560    impl<C, B> crate::Container for Stash<C, B>
561    where
562        C: crate::Container + crate::ContainerBytes,
563        B: core::ops::Deref<Target=[u8]> + Clone + Send + 'static,
564    {
565        fn reserve_for<'a, I>(&mut self, selves: I)
566        where
567            Self: 'a,
568            I: Iterator<Item = Self::Borrowed<'a>> + Clone,
569        {
570            self.make_typed().reserve_for(selves);
571        }
572        #[inline(always)]
573        fn extend_from_self(&mut self, other: Self::Borrowed<'_>, range: core::ops::Range<usize>) {
574            self.make_typed().extend_from_self(other, range);
575        }
576    }
577}
578
579#[cfg(test)]
580mod test {
581    use crate::ContainerOf;
582    use alloc::{vec, vec::Vec, string::{String, ToString}};
583
584    #[test]
585    fn round_trip() {
586
587        use crate::common::{Push, Len, Index};
588        use crate::{Borrow, AsBytes, FromBytes};
589
590        let mut column: ContainerOf<Result<u64, u64>> = Default::default();
591        for i in 0..100u64 {
592            column.push(Ok::<u64, u64>(i));
593            column.push(Err::<u64, u64>(i));
594        }
595
596        assert_eq!(column.len(), 200);
597
598        for i in 0..100 {
599            assert_eq!(column.get(2*i+0), Ok(i as u64));
600            assert_eq!(column.get(2*i+1), Err(i as u64));
601        }
602
603        let column2 = crate::Results::<&[u64], &[u64], &[u64], &[u64], &[u64]>::from_bytes(&mut column.borrow().as_bytes().map(|(_, bytes)| bytes));
604        for i in 0..100 {
605            assert_eq!(column.get(2*i+0), column2.get(2*i+0).copied().map_err(|e| *e));
606            assert_eq!(column.get(2*i+1), column2.get(2*i+1).copied().map_err(|e| *e));
607        }
608
609        let column3 = crate::Results::<&[u64], &[u64], &[u64], &[u64], &[u64]>::from_bytes(&mut column2.as_bytes().map(|(_, bytes)| bytes));
610        for i in 0..100 {
611            assert_eq!(column3.get(2*i+0), column2.get(2*i+0));
612            assert_eq!(column3.get(2*i+1), column2.get(2*i+1));
613        }
614
615        // Test from_store round-trip.
616        let mut store = Vec::new();
617        crate::bytes::indexed::encode(&mut store, &column.borrow());
618        let ds = crate::bytes::indexed::DecodedStore::new(&store);
619        let column4 = crate::Results::<&[u64], &[u64], &[u64], &[u64], &[u64]>::from_store(&ds, &mut 0);
620        for i in 0..100 {
621            assert_eq!(column.get(2*i+0), column4.get(2*i+0).copied().map_err(|e| *e));
622            assert_eq!(column.get(2*i+1), column4.get(2*i+1).copied().map_err(|e| *e));
623        }
624    }
625
626    /// Test that try_from_bytes works for Result, Option, and derived enum types.
627    #[test]
628    fn validate_sum_types() {
629        use crate::common::{Push, Index};
630        use crate::{Borrow, ContainerOf};
631        use crate::bytes::stash::Stash;
632
633        // Result<u64, u64>
634        let mut c: ContainerOf<Result<u64, u64>> = Default::default();
635        for i in 0..100u64 {
636            c.push(Ok::<u64, u64>(i));
637            c.push(Err::<u64, u64>(i));
638        }
639        let mut bytes: Vec<u8> = Vec::new();
640        crate::bytes::indexed::write(&mut bytes, &c.borrow()).unwrap();
641        let stash: Stash<ContainerOf<Result<u64, u64>>, Vec<u8>> =
642            Stash::try_from_bytes(bytes).expect("Result<u64, u64> should validate");
643        assert_eq!(stash.borrow().get(0), Ok(&0u64));
644        assert_eq!(stash.borrow().get(1), Err(&0u64));
645
646        // Option<String>
647        let mut c: ContainerOf<Option<String>> = Default::default();
648        c.push(&Some("hello".to_string()));
649        c.push(&None::<String>);
650        c.push(&Some("world".to_string()));
651        let mut bytes: Vec<u8> = Vec::new();
652        crate::bytes::indexed::write(&mut bytes, &c.borrow()).unwrap();
653        let stash: Stash<ContainerOf<Option<String>>, Vec<u8>> =
654            Stash::try_from_bytes(bytes).expect("Option<String> should validate");
655        assert_eq!(stash.borrow().get(0), Some(&b"hello"[..]));
656        assert_eq!(stash.borrow().get(1), None);
657        assert_eq!(stash.borrow().get(2), Some(&b"world"[..]));
658
659        // Result<(u64, String), u64>
660        let mut c: ContainerOf<Result<(u64, String), u64>> = Default::default();
661        let val: Result<(u64, String), u64> = Ok((42, "test".to_string()));
662        c.push(&val);
663        let val2: Result<(u64, String), u64> = Err(99);
664        c.push(&val2);
665        let mut bytes: Vec<u8> = Vec::new();
666        crate::bytes::indexed::write(&mut bytes, &c.borrow()).unwrap();
667        let stash: Stash<ContainerOf<Result<(u64, String), u64>>, Vec<u8>> =
668            Stash::try_from_bytes(bytes).expect("Result<(u64, String), u64> should validate");
669        let borrowed = stash.borrow();
670        match borrowed.get(0) {
671            Ok((n, s)) => { assert_eq!(*n, 42); assert_eq!(s, b"test"); },
672            Err(_) => panic!("expected Ok"),
673        }
674        match borrowed.get(1) {
675            Err(n) => assert_eq!(*n, 99),
676            Ok(_) => panic!("expected Err"),
677        }
678    }
679
680    /// Test from_store for tuples.
681    #[test]
682    fn from_store_tuple() {
683        use crate::common::{Push, Index};
684        use crate::{Borrow, FromBytes, ContainerOf};
685
686        let mut column: ContainerOf<(u64, String, Vec<u32>)> = Default::default();
687        for i in 0..50u64 {
688            column.push(&(i, format!("hello {i}"), vec![i as u32; i as usize]));
689        }
690
691        let mut store = Vec::new();
692        crate::bytes::indexed::encode(&mut store, &column.borrow());
693        let ds = crate::bytes::indexed::DecodedStore::new(&store);
694        type Borrowed<'a> = crate::BorrowedOf<'a, (u64, String, Vec<u32>)>;
695        let reconstructed = Borrowed::from_store(&ds, &mut 0);
696        for i in 0..50 {
697            let (a, b, _c) = reconstructed.get(i);
698            assert_eq!(*a, i as u64);
699            assert_eq!(b, format!("hello {i}").as_bytes());
700        }
701    }
702
703}