Skip to main content

flatr_core/
read.rs

1//! # flatr/core/src/read.rs
2//!
3//! The read-side of the flatr format: zero-copy access to serialized data.
4//!
5//! ## Design
6//!
7//! No data is copied or allocated during reading.  All accessor methods return
8//! either a primitive value (scalars, bools) or a view struct that borrows
9//! directly from the input byte slice with lifetime `'a`.
10//!
11//! ## Key types
12//!
13//! | Type | Role |
14//! |------|------|
15//! | [`ReadAt`] | Trait: given `buf` and `offset`, decode a value of type `T` |
16//! | [`ListView`] | Zero-copy iterator + random-access view over an array field |
17//! | [`RawView`] | Low-level table accessor: holds `buf`, `t_pos`, `v_pos` |
18//! | [`HasRawView`] | Marker trait on generated `XxxView` types for `merge_table_list` |
19//!
20//! ## Slot vs absolute position
21//!
22//! The write side uses **slots** (distance from the *end* of the buffer) to
23//! identify objects.  The read side uses **absolute byte positions** within
24//! the finished buffer slice.  `RawView::from_slot` converts between the two.
25
26use std::marker::PhantomData;
27use crate::{BitMask, DataType};
28
29// ── ReadAt ────────────────────────────────────────────────────────────────────
30
31/// Decode a value of type `Self` from a byte buffer at a given absolute offset.
32///
33/// `MODE` determines how the parent table locates this field's data:
34/// - `Inline`: the bytes at `field_position` ARE the value.
35/// - `Offset`: the bytes at `field_position` are a u32 forward offset; the
36///   actual data starts at `field_position + forward_offset`.
37///
38/// The lifetime `'a` ties the decoded value to the input buffer so that
39/// zero-copy reads (string slices, list views) compile correctly.
40pub trait ReadAt<'a> {
41    /// Whether the parent table stores this value inline or via a forward offset.
42    const MODE: DataType;
43
44    /// The type returned by `read_at`.  For scalars this is `Self`; for strings
45    /// it is `&'a str`; for arrays it is `ListView<'a, T>`.
46    type ReadOutput;
47
48    /// Decode a value from `buf` at absolute byte position `offset`.
49    fn read_at(buf: &'a [u8], offset: usize) -> Self::ReadOutput;
50
51    // Specialized read method for enums/unions
52    #[inline(always)]
53    fn read_with_tag_at(buf: &'a [u8], offset: usize, _tag:u8) -> Self::ReadOutput {
54        Self::read_at(buf, offset)
55    }
56
57    /// Return the zero/empty default for this type's `ReadOutput`.
58    ///
59    /// Called by [`ListView::get`] when the forward-offset entry for an element
60    /// is `0`, which signals that the element slot is absent.  This only fires
61    /// for `Offset`- and `Union`-mode types; `Inline` types (scalars, `bool`,
62    /// structs) are always present in their list slot, so their `get` path
63    /// never reaches this method.
64    ///
65    /// The default implementation is `unreachable!()` so that `Inline`-mode
66    /// impls pay no code-size cost.  Every `Offset`/`Union` impl must override
67    /// this with the appropriate zero/empty value:
68    ///
69    /// | Type             | Default        |
70    /// |------------------|----------------|
71    /// | `&str` / `String`| `""`           |
72    /// | `Vec<T>`         | empty `ListView`|
73    /// | `FileBlob<T>`    | empty slice view|
74    /// | generated Tables | `View::default()`|
75    /// | generated Unions | `None` variant |
76    fn default_output() -> Self::ReadOutput;
77
78    /// Returns the exclusive end address of the value whose payload starts at
79    /// `pos`.  Overridden by Table (→ view.block_end()), Struct (→ pos +
80    /// size_of), and String (→ pos + 4 + length).  Default returns `pos`
81    /// (safe conservative fallback for scalars/unknown types).
82    #[inline(always)]
83    fn payload_block_end(_buf: &'a [u8], pos: usize) -> usize
84    where Self: Sized { pos + size_of::<Self>() }
85}
86
87// ── Scalars ───────────────────────────────────────────────────────────────────
88
89/// Generates `ReadAt` for all integer types.
90///
91/// Uses `read_unaligned` + `from_le` to safely read a possibly-unaligned
92/// little-endian integer from an arbitrary byte position.  The unsafe block
93/// is sound because `buf.as_ptr().add(offset)` is valid as long as
94/// `offset + size_of::<T>() <= buf.len()`, which callers guarantee.
95macro_rules! impl_read_scalar {
96    ($($t:ty),*) => {$(
97        impl<'a> ReadAt<'a> for $t {
98            const MODE: DataType = DataType::Inline;
99            type ReadOutput = Self;
100            #[inline(always)]
101            fn read_at(buf: &[u8], offset: usize) -> Self {
102                unsafe {
103                    <$t>::from_le(
104                        (buf.as_ptr().add(offset) as *const $t).read_unaligned()
105                    )
106                }
107            }
108
109            #[inline(always)]
110            fn default_output() -> Self {
111                0
112            }
113        }
114    )*};
115}
116
117impl_read_scalar!(u8, u16, u32, u64, i8, i16, i32, i64, u128, i128);
118
119impl<'a> ReadAt<'a> for f32 {
120    const MODE: DataType = DataType::Inline;
121    type ReadOutput = Self;
122    /// Reads the bit pattern as u32 then reinterprets as f32.
123    #[inline(always)]
124    fn read_at(buf: &[u8], offset: usize) -> Self {
125        Self::from_bits(u32::read_at(buf, offset))
126    }
127    #[inline(always)]
128    fn default_output() -> Self {
129        0.
130    }
131}
132
133impl<'a> ReadAt<'a> for f64 {
134    const MODE: DataType = DataType::Inline;
135    type ReadOutput = Self;
136    #[inline(always)]
137    fn read_at(buf: &[u8], offset: usize) -> Self {
138        Self::from_bits(u64::read_at(buf, offset))
139    }
140    #[inline(always)]
141    fn default_output() -> Self {
142        0.
143    }
144}
145
146impl<'a> ReadAt<'a> for bool {
147    const MODE: DataType = DataType::Inline;
148    type ReadOutput = bool;
149    #[inline(always)]
150    fn read_at(buf: &[u8], offset: usize) -> bool {
151        u8::read_at(buf, offset) != 0
152    }
153    #[inline(always)]
154    fn default_output() -> Self {
155        false
156    }
157}
158
159// ── Strings ───────────────────────────────────────────────────────────────────
160
161/// String layout: `[u32 length][UTF-8 bytes...]`
162///
163/// `read_at` is called with the absolute position of the length prefix.
164/// Returns a `&'a str` that borrows directly from the input buffer — no copy.
165///
166/// # Safety
167/// The bytes are assumed to be valid UTF-8 (enforced at write time by
168/// `str::as_bytes`).  Using `from_utf8_unchecked` avoids a redundant
169/// validation scan on every read.
170impl<'a> ReadAt<'a> for &str {
171    const MODE: DataType = DataType::Offset;
172    type ReadOutput = &'a str;
173    #[inline]
174    fn read_at(buf: &'a [u8], offset: usize) -> &'a str {
175        let len = u32::read_at(buf, offset) as usize;
176        unsafe {
177            let bytes = std::slice::from_raw_parts(
178                buf.as_ptr().add(offset + 4), len
179            );
180            std::str::from_utf8_unchecked(bytes)
181        }
182    }
183
184    #[inline(always)]
185    fn default_output() -> &'a str { "" }
186
187    #[inline(always)]
188    fn payload_block_end(buf: &'a [u8], pos: usize) -> usize {
189        pos + 4 + u32::read_at(buf, pos) as usize
190    }
191}
192
193/// Identical to `&str`; exists so that `Vec<String>` fields use the same
194/// read path as `Vec<&str>` fields.  Both return `&'a str`.
195impl<'a> ReadAt<'a> for String {
196    const MODE: DataType = DataType::Offset;
197    type ReadOutput = &'a str;
198    #[inline]
199    fn read_at(buf: &'a [u8], offset: usize) -> &'a str {
200        let len = u32::read_at(buf, offset) as usize;
201        unsafe {
202            let bytes = std::slice::from_raw_parts(
203                buf.as_ptr().add(offset + 4), len
204            );
205            std::str::from_utf8_unchecked(bytes)
206        }
207    }
208
209    #[inline(always)]
210    fn default_output() -> &'a str { "" }
211
212    #[inline(always)]
213    fn payload_block_end(buf: &'a [u8], pos: usize) -> usize {
214        pos + 4 + u32::read_at(buf, pos) as usize
215    }
216}
217
218// ── Vec<T> ────────────────────────────────────────────────────────────────────
219
220/// Reads an array whose length is stored as a u32 length prefix immediately
221/// before the element data (or offset table for indirect elements).
222///
223/// Returns a [`ListView`] that borrows from `buf`.  The ListView's `offset`
224/// points to the first element (or first offset-table entry) — i.e. 4 bytes
225/// past the length prefix.
226impl<'a, T: ReadAt<'a>> ReadAt<'a> for Vec<T> {
227    const MODE: DataType = DataType::Offset;
228    type ReadOutput = ListView<'a, T>;
229
230    #[inline(always)]
231    fn read_at(buf: &'a [u8], offset: usize) -> ListView<'a, T> {
232        ListView::new(buf, offset + 4, u32::read_at(buf, offset) as usize)
233    }
234
235    #[inline(always)]
236    fn default_output() -> ListView<'a, T> { ListView::default() }
237}
238
239/// Reads a fixed-size array `[T; N]` without a length prefix.
240/// Used for const-size inline buffers where `N` is known at compile time.
241impl<'a, T: ReadAt<'a>, const N: usize> ReadAt<'a> for [T; N] {
242    const MODE: DataType = DataType::Offset;
243    type ReadOutput = ListView<'a, T>;
244    #[inline(always)]
245    fn read_at(buf: &'a [u8], offset: usize) -> ListView<'a, T> {
246        ListView::new(buf, offset, N)
247    }
248
249    #[inline(always)]
250    fn default_output() -> ListView<'a, T> { ListView::default() }
251}
252
253// ── ListView ──────────────────────────────────────────────────────────────────
254
255/// A zero-copy, double-ended iterator and random-access view over an array
256/// stored in a flatbuffer.
257///
258/// # Memory layout
259///
260/// For inline elements (`T::MODE == Inline`):
261/// ```text
262/// offset → [elem_0_bytes][elem_1_bytes]...[elem_{len-1}_bytes]
263/// ```
264///
265/// For offset elements (`T::MODE == Offset`):
266/// ```text
267/// offset → [fwd_off_0: u32][fwd_off_1: u32]...[fwd_off_{n-1}: u32]
268///                ↓               ↓
269///            [elem_0 data]   [elem_1 data] ...
270/// ```
271/// Each forward offset is relative to its own position:
272/// `abs_pos(i) = (offset + i*4) + forward_offset[i]`.
273///
274/// # Fields
275///
276/// - `buf`    — the buffer this view borrows from.
277/// - `offset` — absolute byte position of the first element (or offset table).
278/// - `len`    — total number of elements including any skipped ones.
279/// - `next` / `back` — iterator cursors for `Iterator` and `DoubleEndedIterator`.
280///   Both are **indices** into the list (0-based), starting at 0 / len and
281///   converging as elements are consumed.
282/// - `skip`   — unordered set of indices to omit during iteration.  Empty
283///   (`&[]`) when no rows are skipped; the fast path is a single `is_empty()`
284///   check so the non-skipping case is unchanged.
285///
286/// # Skip list
287///
288/// Call [`with_skip`](Self::with_skip) to attach a skip list after construction.
289/// The slice does not need to be sorted.  `get` and `total_len` / `is_empty`
290/// are unaffected — they operate on the raw list.  Only the iterator methods
291/// (`next`, `next_back`) honour the skip list.
292///
293/// When the skip list is active `ExactSizeIterator` is not available because
294/// the exact remaining count depends on how many skip indices fall within
295/// `[next, back)`, which is O(k) to compute.  `size_hint` returns a
296/// conservative upper bound of `back - next`.
297pub struct ListView<'a, T> {
298    pub buf:    &'a [u8],
299    pub offset: usize,
300    pub len:    usize,
301    pub back:   usize,
302    pub next:   usize,
303    /// Unordered indices to skip during iteration.  `&[]` when not skipping.
304    pub skip:   &'a BitMask,
305    _marker: PhantomData<T>,
306}
307static EMPTY_MASK: BitMask = BitMask{
308    bits: Vec::new(),
309    len: 0,
310    count:0,
311};
312impl<'a, T: ReadAt<'a>> ListView<'a, T> {
313    /// Construct a new ListView anchored at `offset` with `len` elements.
314    /// No rows are skipped by default; call [`with_skip`](Self::with_skip) to
315    /// attach a skip list.
316    #[inline(always)]
317    pub fn new(buf: &'a [u8], offset: usize, len: usize) -> Self {
318        Self { buf, offset, len, back: len, next: 0, skip: &EMPTY_MASK, _marker: PhantomData }
319    }
320
321    /// Attach an unordered skip list to this view.
322    ///
323    /// Elements whose index appears anywhere in `skip` are silently omitted
324    /// by `next()` and `next_back()`.  The slice does not need to be sorted.
325    /// `get`, `total_len`, and `is_empty` are not affected.
326    ///
327    /// Replaces any previously attached skip list.
328    #[inline(always)]
329    pub fn with_skip(mut self, skip: &'a BitMask) -> Self {
330        self.skip = skip;
331        self
332    }
333
334    
335    /// Total number of elements in this list, including skipped and already
336    /// iterated ones.
337    #[inline(always)]
338    pub fn total_len(&self) -> usize { self.len }
339    
340    /// Return `true` if the list contains no elements at all (ignores skip list).
341    #[inline(always)]
342    pub fn is_empty(&self) -> bool { self.len == 0 }
343    
344    /// Compute the absolute byte position of element `index`.
345    ///
346    /// For inline types: `offset + index * size_of::<T>()`.
347    /// For offset types: follows the forward-offset entry at
348    ///   `offset + index * 4` to get the element's absolute position.
349    pub fn abs_pos(&self, index: usize) -> usize {
350        if T::MODE.is_inline_flag() {
351            self.offset + index * std::mem::size_of::<T>()
352        } else {
353            let ep   = self.offset + index * 4;
354            let jump = u32::read_at(self.buf, ep) as usize;
355            if jump == 0 { 0 } else { ep + jump }
356        }
357    }
358
359    /// Random access to element at `index`.  Panics if `index >= len`.
360    /// Ignores the skip list — always returns the element at that raw index.
361    ///
362    /// For `Offset`- and `Union`-mode types a forward-offset entry of `0`
363    /// signals an absent element; this returns [`T::default_output()`] with no
364    /// buffer read beyond the entry itself.  `Inline` types (scalars, structs)
365    /// are always present and skip this check entirely — the branch folds away
366    /// at compile time because `T::MODE` is a `const`.
367    #[inline]
368    pub fn get(&self, index: usize) -> T::ReadOutput {
369        let offset = self.abs_pos(index);
370        if offset == 0 {return T::default_output()}
371        let tag = u8::read_at(self.buf,self.offset+ 4*self.total_len() + index);
372        T::read_with_tag_at(self.buf, offset, tag)
373    }
374
375    /// Absolute position of the **last** element.
376    ///
377    /// Used by `merge_string_list` to compute the end of the string pool:
378    /// `last_offset() + 4 + u32::read_at(buf, last_offset())` gives the
379    /// exclusive end of the last string's bytes.
380    #[inline]
381    pub fn last_offset(&self) -> usize {
382        self.abs_pos(self.total_len() - 1)
383    }
384
385    /// Decode and return the last element without advancing the iterator.
386    /// Ignores the skip list.
387    #[inline]
388    pub fn read_last(&self) -> T::ReadOutput {
389        self.get(self.total_len()-1)
390    }
391}
392
393impl<'a, T: ReadAt<'a>> Iterator for ListView<'a, T> {
394    type Item = T::ReadOutput;
395
396    /// Yield the next element from the front (lowest index), honouring the
397    /// skip list.
398    #[inline]
399    fn next(&mut self) -> Option<T::ReadOutput> {
400        if !self.skip.is_empty() {
401            while self.next < self.back && self.skip.is_set(&self.next) {
402                self.next += 1;
403            }
404        }
405        if self.next >= self.back { return None; }
406        let item = self.get(self.next);
407        self.next += 1;
408        Some(item)
409    }
410
411    /// Upper bound is `back - next`; may be an overcount when a skip list is
412    /// active because some of those indices will be silently stepped over.
413    #[inline(always)]
414    fn size_hint(&self) -> (usize, Option<usize>) {
415        let upper = self.back - self.next;
416        if self.skip.is_empty() {
417            (upper, Some(upper))
418        } else {
419            (0, Some(upper))
420        }
421    }
422}
423
424impl<'a, T: ReadAt<'a>> DoubleEndedIterator for ListView<'a, T> {
425    #[inline]
426    fn next_back(&mut self) -> Option<T::ReadOutput> {
427        if !self.skip.is_empty() {
428            while self.next < self.back && self.skip.is_set(&(self.back - 1)) {
429                self.back -= 1;
430            }
431        }
432        if self.next >= self.back { return None; }
433        self.back -= 1;
434        Some(self.get(self.back))
435    }
436}
437/// `ExactSizeIterator` is only implemented when no skip list is active, because
438/// once skips are present the exact remaining count requires an O(k) scan over
439/// the skip slice to count how many entries fall within `[next, back)`.
440///
441/// If you need the precise remaining count with an active skip list, compute it
442/// as: `(back - next) - skip.iter().filter(|&&i| i >= next && i < back).count()`
443impl<'a, T: ReadAt<'a>> ExactSizeIterator for ListView<'a, T> {
444    #[inline(always)]
445    fn len(&self) -> usize {
446        debug_assert!(
447            self.skip.is_empty(),
448            "ExactSizeIterator::len called on a ListView with an active skip list — \
449             result is an overcount; use size_hint or compute manually"
450        );
451        self.back - self.next
452    }
453}
454
455/// `ReadAt` for `ListView` itself — allows nested arrays (`Vec<Vec<T>>`).
456impl<'a, T: ReadAt<'a>> ReadAt<'a> for ListView<'a, T> {
457    const MODE: DataType = DataType::Offset;
458    type ReadOutput = ListView<'a, T>;
459    #[inline]
460    fn read_at(buf: &'a [u8], offset: usize) -> Self::ReadOutput {
461        ListView::new(buf, offset + 4, u32::read_at(buf, offset) as usize)
462    }
463    #[inline(always)]
464    fn default_output() -> ListView<'a, T> { ListView::default() }
465}
466
467/// Collect a `ListView<String>` into an owned `Vec<String>`.
468impl<'a> From<ListView<'a, String>> for Vec<String> {
469    fn from(view: ListView<'a, String>) -> Vec<String> {
470        view.map(|s| s.to_string()).collect()
471    }
472}
473
474/// Collect a `ListView<T>` into an owned `Vec<T>` for scalar/Pod types.
475impl<'a, T: ReadAt<'a, ReadOutput = T> + Clone> From<ListView<'a, T>> for Vec<T> {
476    fn from(view: ListView<'a, T>) -> Vec<T> {
477        view.collect()
478    }
479}
480
481impl<'a, T> Default for ListView<'a, T> {
482    fn default() -> Self {
483        Self { buf: &[], offset: 0, len: 0, back: 0, next: 0, skip: &EMPTY_MASK, _marker: PhantomData }
484    }
485}
486
487// ── RawView ───────────────────────────────────────────────────────────────────
488
489/// Low-level, type-erased view of a single table object.
490///
491/// Holds the three positions needed to navigate any table:
492/// - `buf`   — the entire buffer the table lives in.
493/// - `t_pos` — absolute position of the table object (where the vtable jump is).
494/// - `v_pos` — absolute position of the vtable (computed from the jump).
495///
496/// All generated `XxxView` structs wrap a `RawView` as their inner field and
497/// delegate field lookups to its methods.
498///
499/// # Vtable navigation
500///
501/// ```text
502/// t_pos → [vtable_jump: i32][field_0_data]...[field_n_data]
503///
504/// v_pos = t_pos - vtable_jump   (jump is negative when vtable is before table)
505///
506/// v_pos → [vtable_size: u16][object_size: u16]
507///          [voff_0: u16][voff_1: u16]...[voff_n: u16]
508/// ```
509///
510/// `voff(i) == 0` means field `i` is absent (default value); non-zero means
511/// the field data starts at `t_pos + voff(i)`.
512#[derive(Clone, Copy, Default,)]
513pub struct RawView<'a> {
514    pub buf:   &'a [u8],
515    /// Absolute position of the table object's first byte (the vtable jump).
516    pub t_pos: usize,
517    /// Absolute position of the vtable's first byte.
518    pub v_pos: usize,
519}
520
521impl<'a> RawView<'a> {
522    pub const EMPTY: RawView<'static> = RawView { buf: &[], t_pos: 0, v_pos: 0 };
523    /// Construct a `RawView` given the absolute position of the table object.
524    ///
525    /// The vtable position is computed by reading the signed 32-bit jump stored
526    /// at `table_pos`: `v_pos = table_pos - jump`.
527    #[inline(always)]
528    pub fn new(buf: &'a [u8], table_pos: usize) -> Self {
529        let v_pos = (table_pos as i32 - i32::read_at(buf, table_pos)) as usize;
530        Self { buf, t_pos: table_pos, v_pos }
531    }
532
533    /// Construct from a **slot** (distance from the end of `buf`).
534    ///
535    /// Converts `slot → absolute position` via `buf.len() - slot`, then
536    /// delegates to `new`.
537    #[inline(always)]
538    pub fn from_slot(buf: &'a [u8], slot: usize) -> Self {
539        Self::new(buf, buf.len() - slot)
540    }
541
542    /// Read the vtable offset for field `field_idx`.
543    ///
544    /// The vtable stores one `u16` per field starting at `v_pos + 4` (after
545    /// the 2-byte vtable size and 2-byte object size).  A value of 0 means
546    /// the field is absent.
547    #[inline(always)]
548    pub fn voff(&self, field_idx: usize) -> usize {
549        u16::read_at(self.buf, self.v_pos + 4 + field_idx * 2) as usize
550    }
551
552    /// Return `true` if field `field_idx` is present (vtable offset != 0).
553    #[inline(always)]
554    pub fn is_present(&self, field_idx: usize) -> bool {
555        self.voff(field_idx) != 0
556    }
557
558    /// Follow an `Offset`-mode field: read the forward offset at
559    /// `t_pos + voff(field_idx)` and return the absolute position of the
560    /// pointed-to data.
561    #[inline]
562    pub fn indirect_idx(&self, field_idx: usize) -> usize {
563        let field_pos = self.t_pos + self.voff(field_idx);
564        field_pos + u32::read_at(self.buf, field_pos) as usize
565    }
566
567    #[inline(always)]
568    pub fn vtable_bytes(&self) -> &'a [u8] {
569        let vt_size = u16::read_at(self.buf, self.v_pos) as usize;
570        &self.buf[self.v_pos..self.v_pos + vt_size]
571    }
572}
573
574// ── HasRawView ────────────────────────────────────────────────────────────────
575
576/// Implemented by every generated `XxxView<'a>` type.
577///
578/// This trait exists so that [`merge_table_list`](crate::merge_table_list) can
579/// access the vtable position and block boundaries of a table element view
580/// without knowing its concrete type at compile time.  Without this trait the
581/// generic function would require a type parameter for each table type, causing
582/// a separate monomorphization per list field per table — exactly the
583/// compile-time cost we are trying to avoid.
584///
585/// Both methods are `#[inline(always)]` in the generated impls so the
586/// indirection has no runtime cost.
587pub trait HasRawView<'a> {
588    /// Return a reference to the inner `RawView` for this view.
589    fn raw_view(&self) -> &RawView<'a>;
590
591    /// Return the exclusive end byte position of this table's data block in
592    /// the source buffer.  Equivalent to the generated `block_end()` method.
593    ///
594    /// Used by `merge_table_list` to determine the size of the block to copy
595    /// when merging: `block_size = block_end() - t_pos`.
596    fn block_end_dyn(&self) -> usize;
597}
598
599impl<'a, T: ReadAt<'a>> PartialEq for ListView<'a, T>
600where
601    T::ReadOutput: PartialEq,
602{
603    fn eq(&self, other: &Self) -> bool {
604        if self.total_len() != other.total_len() { return false; }
605        (0..self.total_len()).all(|i| self.get(i) == other.get(i))
606    }
607}
608
609impl<'a, T: ReadAt<'a>> std::fmt::Debug for ListView<'a, T>
610where
611    T::ReadOutput: std::fmt::Debug,
612{
613    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
614        let mut list = f.debug_list();
615        for i in 0..self.total_len() {
616            list.entry(&self.get(i));
617        }
618        list.finish()
619    }
620}
621
622// ── Vec<T> ↔ ListView<T> PartialEq ───────────────────────────────────────────
623
624impl<'a, T: ReadAt<'a>> PartialEq<ListView<'a, T>> for Vec<T>
625where
626    T: PartialEq<T::ReadOutput>,
627{
628    fn eq(&self, other: &ListView<'a, T>) -> bool {
629        if self.len() != other.total_len() { return false; }
630        self.iter().enumerate().all(|(i, v)| *v == other.get(i))
631    }
632}
633
634impl<'a, T: ReadAt<'a>> PartialEq<Vec<T>> for ListView<'a, T>
635where
636    T: PartialEq<T::ReadOutput>,
637{
638    fn eq(&self, other: &Vec<T>) -> bool { other == self }
639}