Skip to main content

wolfram_serialize/
reader.rs

1//! Minimal byte-level reader.
2//!
3//! [`Reader`] is the raw input abstraction: one required method, `read_bytes`,
4//! which hands back a **zero-copy view** into the source — no allocation, no
5//! copy. The view is tied to the buffer lifetime `'de`, so it outlives the
6//! `&mut self` borrow; that is what enables zero-copy *borrowed* deserialization
7//! (a `&'de str` / `&'de [u8]` field can point straight into the input buffer).
8//!
9//! The default [`SliceReader`] is backed by a `&[u8]`, which is exactly the
10//! LibraryLink case (a fully-materialized buffer): build one over
11//! `numeric_array.as_slice()` and WXF values are read straight out of kernel
12//! memory.
13//!
14//! We keep our own trait rather than `std::io::Read` because `io::Read` copies
15//! into a caller buffer and cannot lend a buffer-lifetime view. (And a *copying*
16//! reader would be useless anyway: [`FromWXF`][crate::FromWXF] needs `&'de`
17//! views, which an `io::Read` source can't produce — zero-copy and streaming are
18//! incompatible.)
19
20use crate::Error;
21
22/// Raw byte source that lends **buffer-lifetime** views. `'de` is the lifetime
23/// of the underlying buffer. Reads consume forward; there is no rewind, no peek.
24///
25/// Only slice-backed readers (where all the data is already present) can
26/// implement this, since `read_bytes` must return a view that outlives the
27/// `&mut self` borrow.
28pub trait Reader<'de> {
29    /// Consume `n` bytes, returning a zero-copy view tied to the underlying
30    /// buffer (lifetime `'de`). The copy path treats it as a transient slice;
31    /// the borrow path (`&'de str` / `&'de [u8]`) retains it.
32    fn read_bytes(&mut self, n: usize) -> Result<&'de [u8], Error>;
33
34    /// Consume and return the next byte.
35    fn read_byte(&mut self) -> Result<u8, Error> {
36        Ok(self.read_bytes(1)?[0])
37    }
38}
39
40/// Slice-backed [`Reader`]: holds `&[u8]` plus a position. Every read is a
41/// bounds-checked sub-slice — no allocation, no copy.
42pub struct SliceReader<'a> {
43    bytes: &'a [u8],
44    pos: usize,
45}
46
47impl<'a> SliceReader<'a> {
48    /// Construct over an in-memory byte buffer.
49    pub fn new(bytes: &'a [u8]) -> Self {
50        SliceReader { bytes, pos: 0 }
51    }
52}
53
54impl<'de> Reader<'de> for SliceReader<'de> {
55    fn read_bytes(&mut self, n: usize) -> Result<&'de [u8], Error> {
56        let end = self
57            .pos
58            .checked_add(n)
59            .ok_or_else(|| Error::invalid("byte count overflow".into()))?;
60        // Copy out the `&'de [u8]` reference first so the returned slice is tied
61        // to the buffer lifetime `'de`, not to this `&mut self` borrow.
62        let buf: &'de [u8] = self.bytes;
63        let slice = buf.get(self.pos..end).ok_or_else(|| {
64            Error::invalid(format!("unexpected EOF reading {} bytes", n))
65        })?;
66        self.pos = end;
67        Ok(slice)
68    }
69}