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        // The offset table holds at least one entry (its own end), so `first` is at least 8.
225        // A zero here would otherwise underflow the `- 1` below.
226        if first < 8 || first % 8 != 0 {
227            return Err(format!("first offset {} is not a positive multiple of 8", first));
228        }
229        let slices = first / 8 - 1;
230        if slices + 1 > store.len() {
231            return Err(format!("index requires {} words but store has {}", slices + 1, store.len()));
232        }
233        if slices != expected_slices {
234            return Err(format!("expected {} slices but found {}", expected_slices, slices));
235        }
236        let store_bytes = store.len() * 8;
237        let mut prev_upper = first;
238        for i in 0..slices {
239            let offset = store[i + 1] as usize;
240            if offset > store_bytes {
241                return Err(format!("slice {} offset {} exceeds store size {}", i, offset, store_bytes));
242            }
243            if offset < prev_upper {
244                return Err(format!("slice {} offset {} precedes previous end {}", i, offset, prev_upper));
245            }
246            // Advance prev_upper to the aligned start of the next slice.
247            prev_upper = (offset + 7) & !7;
248        }
249        Ok(())
250    }
251
252    /// Validates that `store` contains well-formed data compatible with type `T`.
253    ///
254    /// Checks both the internal structure of the encoding (offsets, slice count) and
255    /// type-level compatibility (each slice's byte length is a multiple of its element
256    /// size). Call this once at trust boundaries when receiving encoded data.
257    ///
258    /// The `from_store` decode path performs no further validation at access time:
259    /// it will not panic on malformed data, but may return incorrect results.
260    /// There is no undefined behavior in any case. Call this method once before
261    /// using `from_store` to ensure the data is well-formed.
262    ///
263    /// ```ignore
264    /// type B<'a> = <MyContainer as Borrow>::Borrowed<'a>;
265    /// indexed::validate::<B>(&store)?;
266    /// // Now safe to use the non-panicking path:
267    /// let ds = indexed::DecodedStore::new(&store);
268    /// let borrowed = B::from_store(&ds, &mut 0);
269    /// ```
270    pub fn validate<'a, T: crate::FromBytes<'a>>(store: &[u64]) -> Result<(), String> {
271        validate_structure(store, T::SLICE_COUNT)?;
272        let ds = DecodedStore::new(store);
273        let slices: Vec<_> = (0..ds.len()).map(|i| ds.get(i)).collect();
274        T::validate(&slices)
275    }
276
277    /// Decodes a specific byte slice by index. It will be `u64` aligned.
278    #[inline(always)]
279    pub fn decode_index(store: &[u64], index: u64) -> &[u8] {
280        let index = index as usize;
281        let bytes: &[u8] = bytemuck::cast_slice(store);
282        let upper = (store[index + 1] as usize).min(bytes.len());
283        let lower = (((store[index] as usize) + 7) & !7).min(upper);
284        &bytes[lower .. upper]
285    }
286
287    #[cfg(test)]
288    mod test {
289
290        use alloc::{boxed::Box, vec, vec::Vec, string::String};
291        use core::time::Duration;
292        use crate::{Borrow, ContainerOf};
293        use crate::common::Push;
294        use crate::AsBytes;
295
296        use super::{encode, decode};
297
298        fn assert_roundtrip<'a, AB: AsBytes<'a>>(item: &AB) {
299            let mut store = Vec::new();
300            encode(&mut store, item);
301            assert!(item.as_bytes().map(|x| x.1).eq(decode(&store)));
302        }
303
304        #[test]
305        fn round_trip() {
306
307            let mut column: ContainerOf<Result<u64, String>> = Default::default();
308            for i in 0..10000u64 {
309                column.push(&Ok::<u64, String>(i));
310                column.push(&Err::<u64, String>(format!("{:?}", i)));
311            }
312
313            assert_roundtrip(&column.borrow());
314        }
315
316        #[test]
317        fn validate_well_formed() {
318            use crate::common::Push;
319
320            let mut column: ContainerOf<(u64, u64, u64)> = Default::default();
321            for i in 0..100u64 { column.push(&(i, i+1, i+2)); }
322            let mut store = Vec::new();
323            encode(&mut store, &column.borrow());
324
325            type B<'a> = crate::BorrowedOf<'a, (u64, u64, u64)>;
326            assert!(super::validate::<B>(&store).is_ok());
327
328            // Wrong slice count should fail structural validation.
329            assert!(super::validate_structure(&store, 5).is_err());
330        }
331
332        /// Malformed headers are reported as errors rather than panics, including a zero
333        /// first word (which would otherwise underflow when computing the slice count).
334        #[test]
335        fn validate_malformed_header() {
336            assert!(super::validate_structure(&[], 0).is_err());
337            assert!(super::validate_structure(&[0], 0).is_err());
338            assert!(super::validate_structure(&[0, 0, 0], 2).is_err());
339            assert!(super::validate_structure(&[4], 0).is_err());
340            assert!(super::validate_structure(&[16], 1).is_err());
341            assert!(super::validate_structure(&[8], 0).is_ok());
342
343            use crate::bytes::stash::Stash;
344            let stash: Result<Stash<ContainerOf<(u64, String)>, Vec<u8>>, String> = Stash::try_from_bytes(vec![0u8; 16]);
345            assert!(stash.is_err());
346        }
347
348        #[test]
349        fn validate_mixed_types() {
350            use crate::common::Push;
351
352            let mut column: ContainerOf<(u64, String, Vec<u32>)> = Default::default();
353            for i in 0..50u64 {
354                column.push(&(i, format!("hello {i}"), vec![i as u32; i as usize]));
355            }
356            let mut store = Vec::new();
357            encode(&mut store, &column.borrow());
358
359            type B<'a> = crate::BorrowedOf<'a, (u64, String, Vec<u32>)>;
360            assert!(super::validate::<B>(&store).is_ok());
361        }
362
363        #[test]
364        fn validate_primitive_containers() {
365            /// Encodes a container holding the listed items and validates the store.
366            macro_rules! check {
367                ($type:ty, $($item:expr),* $(,)?) => {{
368                    let mut column: ContainerOf<$type> = Default::default();
369                    $(column.push(&$item);)*
370                    let mut store = Vec::new();
371                    encode(&mut store, &column.borrow());
372                    super::validate::<crate::BorrowedOf<'_, $type>>(&store)
373                        .expect(concat!(stringify!($type), " should validate"));
374                }};
375            }
376
377            check!(u128, 0, u128::MAX);
378            check!(i128, i128::MIN, 0, i128::MAX);
379            check!(usize, 0, usize::MAX);
380            check!(isize, isize::MIN, 0, isize::MAX);
381            check!(char, 'a', '\u{1f600}');
382            check!(Duration, Duration::new(0, 0), Duration::new(17, 42));
383            check!(Box<u64>, Box::new(0), Box::new(u64::MAX));
384        }
385
386    }
387}
388
389/// A container of either typed columns, or serialized bytes that can be borrowed as the former.
390pub mod stash {
391
392    use alloc::{vec::Vec, string::String};
393    use crate::{Len, FromBytes};
394    /// A container of either typed columns, or serialized bytes that can be borrowed as the former.
395    ///
396    /// When `B` dereferences to a byte slice, the container can be borrowed as if the container type `C`.
397    /// This container inherits the readable properties of `C` through borrowing, and the implements
398    /// writeable properties by first converting to the `Typed` variant, which is not inexpensive.
399    ///
400    /// The container can be cleared and pushed into. When cleared it reverts to a typed variant, and when
401    /// pushed into if the typed variant it will accept the item, and if not it will panic.
402    ///
403    /// The best ways to construct a `Stash` is with either the `Default` implementation to get an empty
404    /// writeable version, or with the `try_from_bytes` method that attempts to install a type that dereferences
405    /// to a byte slice in the `Bytes` variant, after validating some structural properties.
406    ///
407    /// One can form a `Stash` directly by loading the variants, which are public. Do so with care,
408    /// as loading mis-aligned `B` into the `Bytes` variant can result in a run-time panic, and
409    /// loading structurally invalid data into either the `Bytes` or `Align` variant can produce
410    /// incorrect results at runtime (clamped index accesses, for example). The validation does not
411    /// confirm that the internal structure of types are valid, for example that all vector bounds
412    /// are in-bounds for their values, and these may result in panics at runtime for invalid data.
413    #[derive(Clone)]
414    pub enum Stash<C, B> {
415        /// The typed variant of the container.
416        Typed(C),
417        /// The bytes variant of the container.
418        Bytes(B),
419        /// Relocated, aligned binary data, if `Bytes` doesn't work for some reason.
420        ///
421        /// Most commonly this works around misaligned binary data, but it can also be useful if the `B`
422        /// type is a scarce resource that should be released.
423        Align(alloc::sync::Arc<[u64]>),
424    }
425
426    impl<C: Default, B> Default for Stash<C, B> { fn default() -> Self { Self::Typed(Default::default()) } }
427
428    impl<C: core::fmt::Debug, B: core::ops::Deref<Target=[u8]>> core::fmt::Debug for Stash<C, B> {
429        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
430            match self {
431                Stash::Typed(c) => f.debug_tuple("Typed").field(c).finish(),
432                Stash::Bytes(b) => f.debug_tuple("Bytes").field(&&b[..]).finish(),
433                Stash::Align(a) => f.debug_tuple("Align").field(a).finish(),
434            }
435        }
436    }
437
438    impl<C: crate::ContainerBytes, B: core::ops::Deref<Target = [u8]>> Stash<C, B> {
439        /// An analogue of `TryFrom` for any `B: Deref<Target=[u8]>`, avoiding coherence issues.
440        ///
441        /// This is the recommended way to form a `Stash`, as it performs certain structural validation
442        /// steps that the stash will then skip in future borrowing and indexing operations. If the data
443        /// are structurally invalid, e.g. the wrong framing header, the wrong number of slices for `C`,
444        /// this will return an error. If this returns a `Stash` then all accesses that do not panic should
445        /// be correct. The resulting `Stash` may still panic if the internal structure of the data
446        /// are inconsistent, for example if any vector bounds are out-of-bounds for their values slice.
447        ///
448        /// There is no `unsafe` that is called through this type, and invalid data can result in panics
449        /// or incorrect results, but not undefined behavior.
450        ///
451        /// # Example
452        ///
453        /// ```rust
454        /// use columnar::{Columnar, Borrow, ContainerOf};
455        /// use columnar::common::{Push, Index};
456        /// use columnar::bytes::stash::Stash;
457        ///
458        /// // Build a typed container and populate it.
459        /// let mut stash: Stash<ContainerOf<(u64, String)>, Vec<u8>> = Default::default();
460        /// stash.push(&(0u64, format!("hello")));
461        /// stash.push(&(1u64, format!("world")));
462        ///
463        /// // Serialize to bytes.
464        /// let mut bytes: Vec<u8> = Vec::new();
465        /// stash.write_bytes(&mut bytes);
466        ///
467        /// // Reconstruct from bytes, with validation.
468        /// let stash: Stash<ContainerOf<(u64, String)>, Vec<u8>> =
469        ///     Stash::try_from_bytes(bytes).expect("valid data");
470        ///
471        /// // Borrow and index into individual columns.
472        /// let borrowed = stash.borrow();
473        /// assert_eq!(*Index::get(&borrowed.0, 0), 0u64);
474        /// assert_eq!(borrowed.1.get(1), b"world");
475        /// ```
476        pub fn try_from_bytes(bytes: B) -> Result<Self, String> {
477            use crate::bytes::indexed::validate;
478            use crate::Borrow;
479            if !(bytes.len() % 8 == 0) { return Err(format!("bytes.len() = {:?} not a multiple of 8", bytes.len())) }
480            if let Ok(words) = bytemuck::try_cast_slice::<_, u64>(&bytes) {
481                validate::<<C as Borrow>::Borrowed<'_>>(words)?;
482                Ok(Self::Bytes(bytes))
483            }
484            else {
485                // Re-locating bytes for alignment reasons, in one pass directly into the shared allocation.
486                // (Collecting through a `Vec<u64>` and converting would copy the words a second time.)
487                // The cast to `[u8; 8]` cannot fail: the alignment is one and the length is a multiple of eight.
488                let chunks: &[[u8; 8]] = bytemuck::cast_slice(&bytes[..]);
489                let alloc: alloc::sync::Arc<[u64]> = chunks.iter().map(|chunk| u64::from_ne_bytes(*chunk)).collect();
490                validate::<<C as Borrow>::Borrowed<'_>>(&alloc)?;
491                Ok(Self::Align(alloc))
492            }
493        }
494    }
495
496    impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]> + Clone + 'static> crate::Borrow for Stash<C, B> {
497
498        type Ref<'a> = <C as crate::Borrow>::Ref<'a>;
499        type Borrowed<'a> = <C as crate::Borrow>::Borrowed<'a>;
500
501        #[inline(always)] fn borrow<'a>(&'a self) -> Self::Borrowed<'a> { self.borrow() }
502        #[inline(always)] fn reborrow<'b, 'a: 'b>(item: Self::Borrowed<'a>) -> Self::Borrowed<'b> where Self: 'a { <C as crate::Borrow>::reborrow(item) }
503        #[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) }
504    }
505
506    impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]>> Len for Stash<C, B> {
507        #[inline(always)] fn len(&self) -> usize { self.borrow().len() }
508    }
509
510    impl<C: crate::Container + crate::ContainerBytes, B: core::ops::Deref<Target=[u8]>> Stash<C, B> {
511        /// Converts the stash to the `Typed` variant, by copying the borrowed data into a new container.
512        pub fn to_typed(&self) -> Self {
513            let borrowed = self.borrow();
514            let len = borrowed.len();
515            let mut container = C::with_capacity_for(core::iter::once(borrowed));
516            container.extend_from_self(borrowed, 0..len);
517            Self::Typed(container)
518        }
519        /// Converts the stash to the `Align` variant, by serializing the borrowed data into aligned words.
520        pub fn to_aligned(&self) -> Self {
521            let borrowed = self.borrow();
522            let mut store = Vec::with_capacity(crate::bytes::indexed::length_in_words(&borrowed));
523            crate::bytes::indexed::encode(&mut store, &borrowed);
524            Self::Align(store.into())
525        }
526        /// Ensures the stash is in the `Typed` variant, converting in place if needed, and returns a mutable reference.
527        pub fn make_typed(&mut self) -> &mut C {
528            if !matches!(self, Self::Typed(_)) {
529                *self = self.to_typed();
530            }
531            match self {
532                Stash::Typed(t) => t,
533                _ => unreachable!(),
534            }
535        }
536        /// Ensures the stash is in the `Align` variant, converting in place if needed, and returns a reference.
537        pub fn make_aligned(&mut self) -> &alloc::sync::Arc<[u64]> {
538            if !matches!(self, Self::Align(_)) {
539                *self = self.to_aligned();
540            }
541            match self {
542                Stash::Align(a) => a,
543                _ => unreachable!(),
544            }
545        }
546    }
547
548    impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]>> Stash<C, B> {
549        /// Borrows the contents, either from a typed container or by decoding serialized bytes.
550        ///
551        /// This method is relatively cheap but is not free.
552        #[inline(always)] pub fn borrow<'a>(&'a self) -> <C as crate::Borrow>::Borrowed<'a> {
553            match self {
554                Stash::Typed(t) => t.borrow(),
555                Stash::Bytes(b) => {
556                    let store = crate::bytes::indexed::DecodedStore::new(bytemuck::cast_slice(b));
557                    <C::Borrowed<'_> as FromBytes>::from_store(&store, &mut 0)
558                },
559                Stash::Align(a) => {
560                    let store = crate::bytes::indexed::DecodedStore::new(a);
561                    <C::Borrowed<'_> as FromBytes>::from_store(&store, &mut 0)
562                },
563            }
564        }
565        /// The number of bytes needed to write the contents using the [`indexed`] encoder.
566        ///
567        /// This may be less than the length of the contained bytes or words, if they overshoot.
568        pub fn length_in_bytes(&self) -> usize { crate::bytes::indexed::length_in_bytes(&self.borrow()) }
569        /// Write the contents into a [`WriteBytes`](crate::bytes::WriteBytes) destination.
570        pub fn write_bytes<W: crate::bytes::WriteBytes>(&self, writer: &mut W) -> Result<(), W::Error> {
571            match self {
572                Stash::Typed(t) => { crate::bytes::indexed::write(writer, &t.borrow())?; },
573                Stash::Bytes(b) => writer.write_all(&b[..])?,
574                Stash::Align(a) => writer.write_all(bytemuck::cast_slice(&a[..]))?,
575            }
576            Ok(())
577        }
578    }
579
580    // This implementation converts to owned data if it is not already, which can be expensive.
581    impl<T, C: crate::Container + crate::ContainerBytes + crate::Push<T>, B: core::ops::Deref<Target=[u8]>> crate::Push<T> for Stash<C, B> {
582        fn push(&mut self, item: T) {
583            self.make_typed();
584            match self {
585                Stash::Typed(t) => t.push(item),
586                _ => unreachable!(),
587            }
588        }
589    }
590
591    impl<C: crate::Clear + Default, B> crate::Clear for Stash<C, B> {
592        fn clear(&mut self) {
593            match self {
594                Stash::Typed(t) => t.clear(),
595                Stash::Bytes(_) | Stash::Align(_) => {
596                    *self = Stash::Typed(Default::default());
597                }
598            }
599        }
600    }
601
602    // Both methods materialize to the `Typed` variant before writing, which can be expensive when
603    // starting from `Bytes` or `Align`. This matches the behavior of the `Push` implementation.
604    impl<C, B> crate::Container for Stash<C, B>
605    where
606        C: crate::Container + crate::ContainerBytes,
607        B: core::ops::Deref<Target=[u8]> + Clone + Send + 'static,
608    {
609        fn reserve_for<'a, I>(&mut self, selves: I)
610        where
611            Self: 'a,
612            I: Iterator<Item = Self::Borrowed<'a>> + Clone,
613        {
614            self.make_typed().reserve_for(selves);
615        }
616        #[inline(always)]
617        fn extend_from_self(&mut self, other: Self::Borrowed<'_>, range: core::ops::Range<usize>) {
618            self.make_typed().extend_from_self(other, range);
619        }
620    }
621}
622
623#[cfg(test)]
624mod test {
625    use crate::ContainerOf;
626    use alloc::{vec, vec::Vec, string::{String, ToString}};
627
628    #[test]
629    fn round_trip() {
630
631        use crate::common::{Push, Len, Index};
632        use crate::{Borrow, AsBytes, FromBytes};
633
634        let mut column: ContainerOf<Result<u64, u64>> = Default::default();
635        for i in 0..100u64 {
636            column.push(Ok::<u64, u64>(i));
637            column.push(Err::<u64, u64>(i));
638        }
639
640        assert_eq!(column.len(), 200);
641
642        for i in 0..100 {
643            assert_eq!(column.get(2*i+0), Ok(i as u64));
644            assert_eq!(column.get(2*i+1), Err(i as u64));
645        }
646
647        let column2 = crate::Results::<&[u64], &[u64], &[u64], &[u64], &[u64]>::from_bytes(&mut column.borrow().as_bytes().map(|(_, bytes)| bytes));
648        for i in 0..100 {
649            assert_eq!(column.get(2*i+0), column2.get(2*i+0).copied().map_err(|e| *e));
650            assert_eq!(column.get(2*i+1), column2.get(2*i+1).copied().map_err(|e| *e));
651        }
652
653        let column3 = crate::Results::<&[u64], &[u64], &[u64], &[u64], &[u64]>::from_bytes(&mut column2.as_bytes().map(|(_, bytes)| bytes));
654        for i in 0..100 {
655            assert_eq!(column3.get(2*i+0), column2.get(2*i+0));
656            assert_eq!(column3.get(2*i+1), column2.get(2*i+1));
657        }
658
659        // Test from_store round-trip.
660        let mut store = Vec::new();
661        crate::bytes::indexed::encode(&mut store, &column.borrow());
662        let ds = crate::bytes::indexed::DecodedStore::new(&store);
663        let column4 = crate::Results::<&[u64], &[u64], &[u64], &[u64], &[u64]>::from_store(&ds, &mut 0);
664        for i in 0..100 {
665            assert_eq!(column.get(2*i+0), column4.get(2*i+0).copied().map_err(|e| *e));
666            assert_eq!(column.get(2*i+1), column4.get(2*i+1).copied().map_err(|e| *e));
667        }
668    }
669
670    /// Test that try_from_bytes works for Result, Option, and derived enum types.
671    #[test]
672    fn validate_sum_types() {
673        use crate::common::{Push, Index};
674        use crate::{Borrow, ContainerOf};
675        use crate::bytes::stash::Stash;
676
677        // Result<u64, u64>
678        let mut c: ContainerOf<Result<u64, u64>> = Default::default();
679        for i in 0..100u64 {
680            c.push(Ok::<u64, u64>(i));
681            c.push(Err::<u64, u64>(i));
682        }
683        let mut bytes: Vec<u8> = Vec::new();
684        crate::bytes::indexed::write(&mut bytes, &c.borrow()).unwrap();
685        let stash: Stash<ContainerOf<Result<u64, u64>>, Vec<u8>> =
686            Stash::try_from_bytes(bytes).expect("Result<u64, u64> should validate");
687        assert_eq!(stash.borrow().get(0), Ok(&0u64));
688        assert_eq!(stash.borrow().get(1), Err(&0u64));
689
690        // Option<String>
691        let mut c: ContainerOf<Option<String>> = Default::default();
692        c.push(&Some("hello".to_string()));
693        c.push(&None::<String>);
694        c.push(&Some("world".to_string()));
695        let mut bytes: Vec<u8> = Vec::new();
696        crate::bytes::indexed::write(&mut bytes, &c.borrow()).unwrap();
697        let stash: Stash<ContainerOf<Option<String>>, Vec<u8>> =
698            Stash::try_from_bytes(bytes).expect("Option<String> should validate");
699        assert_eq!(stash.borrow().get(0), Some(&b"hello"[..]));
700        assert_eq!(stash.borrow().get(1), None);
701        assert_eq!(stash.borrow().get(2), Some(&b"world"[..]));
702
703        // Result<(u64, String), u64>
704        let mut c: ContainerOf<Result<(u64, String), u64>> = Default::default();
705        let val: Result<(u64, String), u64> = Ok((42, "test".to_string()));
706        c.push(&val);
707        let val2: Result<(u64, String), u64> = Err(99);
708        c.push(&val2);
709        let mut bytes: Vec<u8> = Vec::new();
710        crate::bytes::indexed::write(&mut bytes, &c.borrow()).unwrap();
711        let stash: Stash<ContainerOf<Result<(u64, String), u64>>, Vec<u8>> =
712            Stash::try_from_bytes(bytes).expect("Result<(u64, String), u64> should validate");
713        let borrowed = stash.borrow();
714        match borrowed.get(0) {
715            Ok((n, s)) => { assert_eq!(*n, 42); assert_eq!(s, b"test"); },
716            Err(_) => panic!("expected Ok"),
717        }
718        match borrowed.get(1) {
719            Err(n) => assert_eq!(*n, 99),
720            Ok(_) => panic!("expected Err"),
721        }
722    }
723
724    /// Bytes that are not `u64`-aligned are relocated into the `Align` variant, and read back correctly.
725    #[test]
726    fn try_from_bytes_misaligned() {
727        use crate::common::{Push, Index, Len};
728        use crate::{Borrow, ContainerOf};
729        use crate::bytes::stash::Stash;
730
731        let mut c: ContainerOf<(u64, String)> = Default::default();
732        for i in 0..100u64 { c.push(&(i, format!("row {i}"))); }
733        let mut bytes: Vec<u8> = Vec::new();
734        crate::bytes::indexed::write(&mut bytes, &c.borrow()).unwrap();
735
736        // Place the encoding at an offset that is not a multiple of 8 within a larger buffer.
737        let mut buffer = vec![0u8; bytes.len() + 16];
738        let offset = if (buffer.as_ptr() as usize + 1) % 8 == 0 { 2 } else { 1 };
739        buffer[offset .. offset + bytes.len()].copy_from_slice(&bytes);
740        let misaligned: &[u8] = &buffer[offset .. offset + bytes.len()];
741        assert_ne!(misaligned.as_ptr() as usize % 8, 0);
742
743        let stash: Stash<ContainerOf<(u64, String)>, &[u8]> = Stash::try_from_bytes(misaligned).expect("misaligned bytes should relocate");
744        assert!(matches!(stash, Stash::Align(_)));
745        let borrowed = stash.borrow();
746        assert_eq!(borrowed.len(), 100);
747        for i in 0..100u64 {
748            let (n, s) = borrowed.get(i as usize);
749            assert_eq!(*n, i);
750            assert_eq!(s, format!("row {i}").as_bytes());
751        }
752    }
753
754    /// Test from_store for tuples.
755    #[test]
756    fn from_store_tuple() {
757        use crate::common::{Push, Index};
758        use crate::{Borrow, FromBytes, ContainerOf};
759
760        let mut column: ContainerOf<(u64, String, Vec<u32>)> = Default::default();
761        for i in 0..50u64 {
762            column.push(&(i, format!("hello {i}"), vec![i as u32; i as usize]));
763        }
764
765        let mut store = Vec::new();
766        crate::bytes::indexed::encode(&mut store, &column.borrow());
767        let ds = crate::bytes::indexed::DecodedStore::new(&store);
768        type Borrowed<'a> = crate::BorrowedOf<'a, (u64, String, Vec<u32>)>;
769        let reconstructed = Borrowed::from_store(&ds, &mut 0);
770        for i in 0..50 {
771            let (a, b, _c) = reconstructed.get(i);
772            assert_eq!(*a, i as u64);
773            assert_eq!(b, format!("hello {i}").as_bytes());
774        }
775    }
776
777}