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 inherents the readable properties of `C` through borrowing, but does not implement
356    /// the traits itself.
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: crate::ContainerBytes, B: core::ops::Deref<Target = [u8]>> Stash<C, B> {
387        /// An analogue of `TryFrom` for any `B: Deref<Target=[u8]>`, avoiding coherence issues.
388        ///
389        /// This is the recommended way to form a `Stash`, as it performs certain structural validation
390        /// steps that the stash will then skip in future borrowing and indexing operations. If the data
391        /// are structurally invalid, e.g. the wrong framing header, the wrong number of slices for `C`,
392        /// this will return an error. If this returns a `Stash` then all accesses that do not panic should
393        /// be correct. The resulting `Stash` may still panic if the internal structure of the data
394        /// are inconsistent, for example if any vector bounds are out-of-bounds for their values slice.
395        ///
396        /// There is no `unsafe` that is called through this type, and invalid data can result in panics
397        /// or incorrect results, but not undefined behavior.
398        ///
399        /// # Example
400        ///
401        /// ```rust
402        /// use columnar::{Columnar, Borrow, ContainerOf};
403        /// use columnar::common::{Push, Index};
404        /// use columnar::bytes::stash::Stash;
405        ///
406        /// // Build a typed container and populate it.
407        /// let mut stash: Stash<ContainerOf<(u64, String)>, Vec<u8>> = Default::default();
408        /// stash.push(&(0u64, format!("hello")));
409        /// stash.push(&(1u64, format!("world")));
410        ///
411        /// // Serialize to bytes.
412        /// let mut bytes: Vec<u8> = Vec::new();
413        /// stash.write_bytes(&mut bytes);
414        ///
415        /// // Reconstruct from bytes, with validation.
416        /// let stash: Stash<ContainerOf<(u64, String)>, Vec<u8>> =
417        ///     Stash::try_from_bytes(bytes).expect("valid data");
418        ///
419        /// // Borrow and index into individual columns.
420        /// let borrowed = stash.borrow();
421        /// assert_eq!(*Index::get(&borrowed.0, 0), 0u64);
422        /// assert_eq!(borrowed.1.get(1), b"world");
423        /// ```
424        pub fn try_from_bytes(bytes: B) -> Result<Self, String> {
425            use crate::bytes::indexed::validate;
426            use crate::Borrow;
427            if !(bytes.len() % 8 == 0) { return Err(format!("bytes.len() = {:?} not a multiple of 8", bytes.len())) }
428            if let Ok(words) = bytemuck::try_cast_slice::<_, u64>(&bytes) {
429                validate::<<C as Borrow>::Borrowed<'_>>(words)?;
430                Ok(Self::Bytes(bytes))
431            }
432            else {
433                // Re-locating bytes for alignment reasons.
434                let mut alloc: Vec<u64> = vec![0; bytes.len() / 8];
435                bytemuck::cast_slice_mut(&mut alloc[..]).copy_from_slice(&bytes[..]);
436                validate::<<C as Borrow>::Borrowed<'_>>(&alloc)?;
437                Ok(Self::Align(alloc.into()))
438            }
439        }
440    }
441
442    impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]> + Clone + 'static> crate::Borrow for Stash<C, B> {
443
444        type Ref<'a> = <C as crate::Borrow>::Ref<'a>;
445        type Borrowed<'a> = <C as crate::Borrow>::Borrowed<'a>;
446
447        #[inline(always)] fn borrow<'a>(&'a self) -> Self::Borrowed<'a> { self.borrow() }
448        #[inline(always)] fn reborrow<'b, 'a: 'b>(item: Self::Borrowed<'a>) -> Self::Borrowed<'b> where Self: 'a { <C as crate::Borrow>::reborrow(item) }
449        #[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) }
450    }
451
452    impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]>> Len for Stash<C, B> {
453        #[inline(always)] fn len(&self) -> usize { self.borrow().len() }
454    }
455
456    impl<C: crate::Container + crate::ContainerBytes, B: core::ops::Deref<Target=[u8]>> Stash<C, B> {
457        /// Converts the stash to the `Typed` variant, by copying the borrowed data into a new container.
458        pub fn to_typed(&self) -> Self {
459            let borrowed = self.borrow();
460            let len = borrowed.len();
461            let mut container = C::with_capacity_for(core::iter::once(borrowed));
462            container.extend_from_self(borrowed, 0..len);
463            Self::Typed(container)
464        }
465        /// Converts the stash to the `Align` variant, by serializing the borrowed data into aligned words.
466        pub fn to_aligned(&self) -> Self {
467            let borrowed = self.borrow();
468            let mut store = Vec::with_capacity(crate::bytes::indexed::length_in_words(&borrowed));
469            crate::bytes::indexed::encode(&mut store, &borrowed);
470            Self::Align(store.into())
471        }
472        /// Ensures the stash is in the `Typed` variant, converting in place if needed, and returns a mutable reference.
473        pub fn make_typed(&mut self) -> &mut C {
474            if !matches!(self, Self::Typed(_)) {
475                *self = self.to_typed();
476            }
477            match self {
478                Stash::Typed(t) => t,
479                _ => unreachable!(),
480            }
481        }
482        /// Ensures the stash is in the `Align` variant, converting in place if needed, and returns a reference.
483        pub fn make_aligned(&mut self) -> &alloc::sync::Arc<[u64]> {
484            if !matches!(self, Self::Align(_)) {
485                *self = self.to_aligned();
486            }
487            match self {
488                Stash::Align(a) => a,
489                _ => unreachable!(),
490            }
491        }
492    }
493
494    impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]>> Stash<C, B> {
495        /// Borrows the contents, either from a typed container or by decoding serialized bytes.
496        ///
497        /// This method is relatively cheap but is not free.
498        #[inline(always)] pub fn borrow<'a>(&'a self) -> <C as crate::Borrow>::Borrowed<'a> {
499            match self {
500                Stash::Typed(t) => t.borrow(),
501                Stash::Bytes(b) => {
502                    let store = crate::bytes::indexed::DecodedStore::new(bytemuck::cast_slice(b));
503                    <C::Borrowed<'_> as FromBytes>::from_store(&store, &mut 0)
504                },
505                Stash::Align(a) => {
506                    let store = crate::bytes::indexed::DecodedStore::new(a);
507                    <C::Borrowed<'_> as FromBytes>::from_store(&store, &mut 0)
508                },
509            }
510        }
511        /// The number of bytes needed to write the contents using the [`indexed`] encoder.
512        ///
513        /// This may be less than the length of the contained bytes or words, if they overshoot.
514        pub fn length_in_bytes(&self) -> usize { crate::bytes::indexed::length_in_bytes(&self.borrow()) }
515        /// Write the contents into a [`WriteBytes`](crate::bytes::WriteBytes) destination.
516        pub fn write_bytes<W: crate::bytes::WriteBytes>(&self, writer: &mut W) -> Result<(), W::Error> {
517            match self {
518                Stash::Typed(t) => { crate::bytes::indexed::write(writer, &t.borrow())?; },
519                Stash::Bytes(b) => writer.write_all(&b[..])?,
520                Stash::Align(a) => writer.write_all(bytemuck::cast_slice(&a[..]))?,
521            }
522            Ok(())
523        }
524    }
525
526    // This implementation converts to owned data if it is not already, which can be expensive.
527    impl<T, C: crate::Container + crate::ContainerBytes + crate::Push<T>, B: core::ops::Deref<Target=[u8]>> crate::Push<T> for Stash<C, B> {
528        fn push(&mut self, item: T) {
529            self.make_typed();
530            match self {
531                Stash::Typed(t) => t.push(item),
532                _ => unreachable!(),
533            }
534        }
535    }
536
537    impl<C: crate::Clear + Default, B> crate::Clear for Stash<C, B> {
538        fn clear(&mut self) {
539            match self {
540                Stash::Typed(t) => t.clear(),
541                Stash::Bytes(_) | Stash::Align(_) => {
542                    *self = Stash::Typed(Default::default());
543                }
544            }
545        }
546    }
547}
548
549#[cfg(test)]
550mod test {
551    use crate::ContainerOf;
552    use alloc::{vec, vec::Vec, string::{String, ToString}};
553
554    #[test]
555    fn round_trip() {
556
557        use crate::common::{Push, Len, Index};
558        use crate::{Borrow, AsBytes, FromBytes};
559
560        let mut column: ContainerOf<Result<u64, u64>> = Default::default();
561        for i in 0..100u64 {
562            column.push(Ok::<u64, u64>(i));
563            column.push(Err::<u64, u64>(i));
564        }
565
566        assert_eq!(column.len(), 200);
567
568        for i in 0..100 {
569            assert_eq!(column.get(2*i+0), Ok(i as u64));
570            assert_eq!(column.get(2*i+1), Err(i as u64));
571        }
572
573        let column2 = crate::Results::<&[u64], &[u64], &[u64], &[u64], &[u64]>::from_bytes(&mut column.borrow().as_bytes().map(|(_, bytes)| bytes));
574        for i in 0..100 {
575            assert_eq!(column.get(2*i+0), column2.get(2*i+0).copied().map_err(|e| *e));
576            assert_eq!(column.get(2*i+1), column2.get(2*i+1).copied().map_err(|e| *e));
577        }
578
579        let column3 = crate::Results::<&[u64], &[u64], &[u64], &[u64], &[u64]>::from_bytes(&mut column2.as_bytes().map(|(_, bytes)| bytes));
580        for i in 0..100 {
581            assert_eq!(column3.get(2*i+0), column2.get(2*i+0));
582            assert_eq!(column3.get(2*i+1), column2.get(2*i+1));
583        }
584
585        // Test from_store round-trip.
586        let mut store = Vec::new();
587        crate::bytes::indexed::encode(&mut store, &column.borrow());
588        let ds = crate::bytes::indexed::DecodedStore::new(&store);
589        let column4 = crate::Results::<&[u64], &[u64], &[u64], &[u64], &[u64]>::from_store(&ds, &mut 0);
590        for i in 0..100 {
591            assert_eq!(column.get(2*i+0), column4.get(2*i+0).copied().map_err(|e| *e));
592            assert_eq!(column.get(2*i+1), column4.get(2*i+1).copied().map_err(|e| *e));
593        }
594    }
595
596    /// Test that try_from_bytes works for Result, Option, and derived enum types.
597    #[test]
598    fn validate_sum_types() {
599        use crate::common::{Push, Index};
600        use crate::{Borrow, ContainerOf};
601        use crate::bytes::stash::Stash;
602
603        // Result<u64, u64>
604        let mut c: ContainerOf<Result<u64, u64>> = Default::default();
605        for i in 0..100u64 {
606            c.push(Ok::<u64, u64>(i));
607            c.push(Err::<u64, u64>(i));
608        }
609        let mut bytes: Vec<u8> = Vec::new();
610        crate::bytes::indexed::write(&mut bytes, &c.borrow()).unwrap();
611        let stash: Stash<ContainerOf<Result<u64, u64>>, Vec<u8>> =
612            Stash::try_from_bytes(bytes).expect("Result<u64, u64> should validate");
613        assert_eq!(stash.borrow().get(0), Ok(&0u64));
614        assert_eq!(stash.borrow().get(1), Err(&0u64));
615
616        // Option<String>
617        let mut c: ContainerOf<Option<String>> = Default::default();
618        c.push(&Some("hello".to_string()));
619        c.push(&None::<String>);
620        c.push(&Some("world".to_string()));
621        let mut bytes: Vec<u8> = Vec::new();
622        crate::bytes::indexed::write(&mut bytes, &c.borrow()).unwrap();
623        let stash: Stash<ContainerOf<Option<String>>, Vec<u8>> =
624            Stash::try_from_bytes(bytes).expect("Option<String> should validate");
625        assert_eq!(stash.borrow().get(0), Some(&b"hello"[..]));
626        assert_eq!(stash.borrow().get(1), None);
627        assert_eq!(stash.borrow().get(2), Some(&b"world"[..]));
628
629        // Result<(u64, String), u64>
630        let mut c: ContainerOf<Result<(u64, String), u64>> = Default::default();
631        let val: Result<(u64, String), u64> = Ok((42, "test".to_string()));
632        c.push(&val);
633        let val2: Result<(u64, String), u64> = Err(99);
634        c.push(&val2);
635        let mut bytes: Vec<u8> = Vec::new();
636        crate::bytes::indexed::write(&mut bytes, &c.borrow()).unwrap();
637        let stash: Stash<ContainerOf<Result<(u64, String), u64>>, Vec<u8>> =
638            Stash::try_from_bytes(bytes).expect("Result<(u64, String), u64> should validate");
639        let borrowed = stash.borrow();
640        match borrowed.get(0) {
641            Ok((n, s)) => { assert_eq!(*n, 42); assert_eq!(s, b"test"); },
642            Err(_) => panic!("expected Ok"),
643        }
644        match borrowed.get(1) {
645            Err(n) => assert_eq!(*n, 99),
646            Ok(_) => panic!("expected Err"),
647        }
648    }
649
650    /// Test from_store for tuples.
651    #[test]
652    fn from_store_tuple() {
653        use crate::common::{Push, Index};
654        use crate::{Borrow, FromBytes, ContainerOf};
655
656        let mut column: ContainerOf<(u64, String, Vec<u32>)> = Default::default();
657        for i in 0..50u64 {
658            column.push(&(i, format!("hello {i}"), vec![i as u32; i as usize]));
659        }
660
661        let mut store = Vec::new();
662        crate::bytes::indexed::encode(&mut store, &column.borrow());
663        let ds = crate::bytes::indexed::DecodedStore::new(&store);
664        type Borrowed<'a> = crate::BorrowedOf<'a, (u64, String, Vec<u32>)>;
665        let reconstructed = Borrowed::from_store(&ds, &mut 0);
666        for i in 0..50 {
667            let (a, b, _c) = reconstructed.get(i);
668            assert_eq!(*a, i as u64);
669            assert_eq!(b, format!("hello {i}").as_bytes());
670        }
671    }
672
673}