farena 0.1.0

A file-backed arena allocator using pread for RSS-conscious byte storage
Documentation
use std::fs::File;
use std::io::{Error, Result};
use std::os::unix::fs::FileExt;

use crate::Location;

/// Read-phase arena that retrieves stored data via `pread(2)`.
///
/// Constructed from file handles returned by
/// [`FileArenaWriter::finish`](crate::FileArenaWriter::finish).
/// Reads are atomic and require no seeking, making `FileArena` safe
/// to share across threads via `&FileArena`.
pub struct FileArena {
    files: Vec<File>,
}

impl FileArena {
    /// Creates a new arena from a list of file handles.
    pub fn new(files: Vec<File>) -> Result<Self> {
        Ok(Self { files })
    }

    /// Reads stored bytes at the given location.
    pub fn get(&self, loc: Location) -> Result<Vec<u8>> {
        if loc.len == 0 {
            return Ok(Vec::new());
        }
        let mut buf = vec![0u8; loc.len as usize];
        let bytes_read =
            self.files[loc.file_index as usize].read_at(&mut buf, loc.offset as u64)?;
        if bytes_read != buf.len() {
            return Err(Error::other(format!(
                "short read: expected {} bytes, got {} at offset {}",
                buf.len(),
                bytes_read,
                loc.offset
            )));
        }
        Ok(buf)
    }

    /// Reads stored bytes, appending into an existing buffer.
    ///
    /// Reuses the buffer's capacity to avoid repeated allocation.
    pub fn get_into(&self, loc: Location, out: &mut Vec<u8>) -> Result<()> {
        if loc.len == 0 {
            return Ok(());
        }
        let len = loc.len as usize;
        out.reserve(len);
        let start = out.len();
        // SAFETY: we are about to fill these bytes via read_at
        unsafe {
            out.set_len(start + len);
        }
        let bytes_read =
            self.files[loc.file_index as usize].read_at(&mut out[start..], loc.offset as u64)?;
        if bytes_read != len {
            // Restore original length since we failed to read all bytes
            out.truncate(start);
            return Err(Error::other(format!(
                "short read: expected {} bytes, got {} at offset {}",
                len, bytes_read, loc.offset
            )));
        }
        Ok(())
    }

    /// Returns the number of backing files.
    pub fn file_count(&self) -> usize {
        self.files.len()
    }
}