Skip to main content

cyanea_core/
mmap.rs

1//! Memory-mapped file access for zero-copy I/O.
2//!
3//! Only available with the `std` feature (disabled for WASM).
4
5use memmap2::Mmap;
6use std::fs::File;
7use std::path::Path;
8
9use crate::{CyaneaError, Result};
10
11/// A read-only memory-mapped file.
12pub struct MappedFile {
13    _file: File,
14    mmap: Mmap,
15}
16
17impl MappedFile {
18    /// Open and memory-map a file.
19    ///
20    /// # Safety
21    ///
22    /// The caller must ensure that the file is not modified by another process
23    /// while the mapping is active. This is safe for immutable data files
24    /// (FASTA, BAM, etc.) which are the primary use case.
25    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
26        let path = path.as_ref();
27        let file = File::open(path).map_err(|e| {
28            CyaneaError::Io(std::io::Error::new(
29                e.kind(),
30                format!("{}: {}", path.display(), e),
31            ))
32        })?;
33        // SAFETY: We hold the File open for the lifetime of the mapping.
34        // The caller is responsible for ensuring no concurrent modification.
35        let mmap = unsafe { Mmap::map(&file) }?;
36        Ok(Self { _file: file, mmap })
37    }
38
39    /// The mapped bytes.
40    pub fn as_bytes(&self) -> &[u8] {
41        &self.mmap
42    }
43
44    /// Length in bytes.
45    pub fn len(&self) -> usize {
46        self.mmap.len()
47    }
48
49    /// Whether the mapped region is empty.
50    pub fn is_empty(&self) -> bool {
51        self.mmap.is_empty()
52    }
53}
54
55impl AsRef<[u8]> for MappedFile {
56    fn as_ref(&self) -> &[u8] {
57        self.as_bytes()
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use std::io::Write;
65
66    #[test]
67    fn test_mmap_roundtrip() {
68        let mut file = tempfile::NamedTempFile::new().unwrap();
69        file.write_all(b"hello mmap").unwrap();
70        file.flush().unwrap();
71
72        let mapped = MappedFile::open(file.path()).unwrap();
73        assert_eq!(mapped.as_bytes(), b"hello mmap");
74        assert_eq!(mapped.len(), 10);
75        assert!(!mapped.is_empty());
76    }
77
78    #[test]
79    fn test_mmap_not_found() {
80        let result = MappedFile::open("/nonexistent/file.txt");
81        assert!(result.is_err());
82    }
83}