farena 0.1.0

A file-backed arena allocator using pread for RSS-conscious byte storage
Documentation
use std::fs::File;
use std::io::{BufWriter, Write};

/// A writer for appending data to an anonymous temp file.
///
/// Data is written sequentially during the write phase. The file can then be
/// passed to `FileArena` for random reads via `pread`.
pub struct FileArenaWriter {
    file: File,
    writer: BufWriter<File>,
    cursor: usize,
}

impl FileArenaWriter {
    /// Creates a new writer backed by an anonymous temp file.
    pub fn new() -> std::io::Result<Self> {
        let file = tempfile::tempfile()?;
        let writer = BufWriter::new(file.try_clone()?);
        Ok(Self {
            file,
            writer,
            cursor: 0,
        })
    }

    /// Appends data to the file and returns `(offset, len)`.
    ///
    /// For empty input, returns `(0, 0)` without writing anything.
    pub fn push(&mut self, data: impl AsRef<[u8]>) -> std::io::Result<(usize, usize)> {
        let data = data.as_ref();

        if data.is_empty() {
            return Ok((0, 0));
        }

        let offset = self.cursor;
        self.writer.write_all(data)?;
        self.cursor += data.len();

        Ok((offset, data.len()))
    }

    /// Returns the current cursor position (total bytes written).
    pub fn len(&self) -> usize {
        self.cursor
    }

    /// Returns true if no data has been written.
    pub fn is_empty(&self) -> bool {
        self.cursor == 0
    }

    /// Flushes the writer and returns the underlying file.
    ///
    /// Returns `None` if no data was written (empty arena).
    pub fn finish(mut self) -> std::io::Result<Option<File>> {
        self.writer.flush()?;
        if self.is_empty() {
            Ok(None)
        } else {
            Ok(Some(self.file))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn push_empty_is_noop() {
        let mut writer = FileArenaWriter::new().unwrap();
        let (offset, len) = writer.push("").unwrap();
        assert_eq!(offset, 0);
        assert_eq!(len, 0);
        assert!(writer.is_empty());
    }

    #[test]
    fn finish_returns_none_when_empty() {
        let writer = FileArenaWriter::new().unwrap();
        assert!(writer.finish().unwrap().is_none());
    }

    #[test]
    fn finish_returns_none_after_only_empty_pushes() {
        let mut writer = FileArenaWriter::new().unwrap();
        writer.push("").unwrap();
        writer.push(b"").unwrap();
        assert!(writer.finish().unwrap().is_none());
    }
}