mmap_io/
iterator.rs

1//! Iterator-based access for efficient sequential processing of memory-mapped files.
2
3use crate::errors::Result;
4use crate::mmap::MemoryMappedFile;
5use crate::utils::page_size;
6use std::marker::PhantomData;
7
8/// Iterator over fixed-size chunks of a memory-mapped file.
9///
10/// # Examples
11///
12/// ```no_run
13/// use mmap_io::MemoryMappedFile;
14///
15/// let mmap = MemoryMappedFile::open_ro("data.bin")?;
16/// 
17/// // Iterate over 4KB chunks
18/// for (offset, chunk) in mmap.chunks(4096).enumerate() {
19///     let chunk_data = chunk?;
20///     println!("Chunk {} at offset {}: {} bytes", 
21///              offset, offset * 4096, chunk_data.len());
22/// }
23/// # Ok::<(), mmap_io::MmapIoError>(())
24/// ```
25pub struct ChunkIterator<'a> {
26    mmap: &'a MemoryMappedFile,
27    chunk_size: usize,
28    current_offset: u64,
29    total_len: u64,
30}
31
32impl<'a> ChunkIterator<'a> {
33    /// Create a new chunk iterator.
34    pub(crate) fn new(mmap: &'a MemoryMappedFile, chunk_size: usize) -> Result<Self> {
35        let total_len = mmap.current_len()?;
36        Ok(Self {
37            mmap,
38            chunk_size,
39            current_offset: 0,
40            total_len,
41        })
42    }
43}
44
45impl<'a> Iterator for ChunkIterator<'a> {
46    type Item = Result<Vec<u8>>;
47
48    fn next(&mut self) -> Option<Self::Item> {
49        if self.current_offset >= self.total_len {
50            return None;
51        }
52
53        let remaining = self.total_len - self.current_offset;
54        let chunk_len = remaining.min(self.chunk_size as u64);
55
56        // For RW mappings, we need to use read_into
57        let mut buffer = vec![0u8; chunk_len as usize];
58        match self.mmap.read_into(self.current_offset, &mut buffer) {
59            Ok(()) => {
60                self.current_offset += chunk_len;
61                Some(Ok(buffer))
62            }
63            Err(e) => Some(Err(e)),
64        }
65    }
66
67    fn size_hint(&self) -> (usize, Option<usize>) {
68        let remaining = self.total_len.saturating_sub(self.current_offset);
69        let chunks = (remaining as usize).div_ceil(self.chunk_size);
70        (chunks, Some(chunks))
71    }
72}
73
74impl<'a> ExactSizeIterator for ChunkIterator<'a> {}
75
76/// Iterator over page-aligned chunks of a memory-mapped file.
77///
78/// Pages are aligned to the system's page size for optimal performance.
79///
80/// # Examples
81///
82/// ```no_run
83/// use mmap_io::MemoryMappedFile;
84///
85/// let mmap = MemoryMappedFile::open_ro("data.bin")?;
86/// 
87/// // Iterate over system pages
88/// for page in mmap.pages() {
89///     let page_data = page?;
90///     // Process page...
91/// }
92/// # Ok::<(), mmap_io::MmapIoError>(())
93/// ```
94pub struct PageIterator<'a> {
95    inner: ChunkIterator<'a>,
96}
97
98impl<'a> PageIterator<'a> {
99    /// Create a new page iterator.
100    pub(crate) fn new(mmap: &'a MemoryMappedFile) -> Result<Self> {
101        let ps = page_size();
102        Ok(Self {
103            inner: ChunkIterator::new(mmap, ps)?,
104        })
105    }
106}
107
108impl<'a> Iterator for PageIterator<'a> {
109    type Item = Result<Vec<u8>>;
110
111    fn next(&mut self) -> Option<Self::Item> {
112        self.inner.next()
113    }
114
115    fn size_hint(&self) -> (usize, Option<usize>) {
116        self.inner.size_hint()
117    }
118}
119
120impl<'a> ExactSizeIterator for PageIterator<'a> {}
121
122/// Mutable iterator over fixed-size chunks of a memory-mapped file.
123///
124/// This iterator provides mutable access to chunks, but due to Rust's borrowing
125/// rules, it cannot yield multiple mutable references simultaneously. Instead,
126/// it provides a callback-based interface.
127pub struct ChunkIteratorMut<'a> {
128    mmap: &'a MemoryMappedFile,
129    chunk_size: usize,
130    current_offset: u64,
131    total_len: u64,
132    _phantom: PhantomData<&'a mut [u8]>,
133}
134
135impl<'a> ChunkIteratorMut<'a> {
136    /// Create a new mutable chunk iterator.
137    pub(crate) fn new(mmap: &'a MemoryMappedFile, chunk_size: usize) -> Result<Self> {
138        let total_len = mmap.current_len()?;
139        Ok(Self {
140            mmap,
141            chunk_size,
142            current_offset: 0,
143            total_len,
144            _phantom: PhantomData,
145        })
146    }
147
148    /// Process each chunk with a callback function.
149    ///
150    /// The callback receives the offset and a mutable slice for each chunk.
151    pub fn for_each_mut<F, E>(mut self, mut f: F) -> Result<std::result::Result<(), E>>
152    where
153        F: FnMut(u64, &mut [u8]) -> std::result::Result<(), E>,
154    {
155        while self.current_offset < self.total_len {
156            let remaining = self.total_len - self.current_offset;
157            let chunk_len = remaining.min(self.chunk_size as u64);
158
159            let mut guard = self.mmap.as_slice_mut(self.current_offset, chunk_len)?;
160            let slice = guard.as_mut();
161            
162            match f(self.current_offset, slice) {
163                Ok(()) => {},
164                Err(e) => return Ok(Err(e)),
165            }
166
167            self.current_offset += chunk_len;
168        }
169        Ok(Ok(()))
170    }
171}
172
173impl MemoryMappedFile {
174    /// Create an iterator over fixed-size chunks of the file.
175    ///
176    /// For read-only and copy-on-write mappings, this returns immutable slices.
177    /// For read-write mappings, use `chunks_mut()` for mutable access.
178    ///
179    /// # Arguments
180    ///
181    /// * `chunk_size` - Size of each chunk in bytes
182    ///
183    /// # Examples
184    ///
185    /// ```no_run
186    /// use mmap_io::MemoryMappedFile;
187    ///
188    /// let mmap = MemoryMappedFile::open_ro("data.bin")?;
189    /// 
190    /// // Process file in 1MB chunks
191    /// for chunk in mmap.chunks(1024 * 1024) {
192    ///     let data = chunk?;
193    ///     // Process chunk...
194    /// }
195    /// # Ok::<(), mmap_io::MmapIoError>(())
196    /// ```
197    #[cfg(feature = "iterator")]
198    pub fn chunks(&self, chunk_size: usize) -> ChunkIterator<'_> {
199        ChunkIterator::new(self, chunk_size).expect("chunk iterator creation should not fail")
200    }
201
202    /// Create an iterator over page-aligned chunks of the file.
203    ///
204    /// Pages are aligned to the system's page size, which is typically 4KB on most systems.
205    /// This can provide better performance for certain access patterns.
206    ///
207    /// # Examples
208    ///
209    /// ```no_run
210    /// use mmap_io::MemoryMappedFile;
211    ///
212    /// let mmap = MemoryMappedFile::open_ro("data.bin")?;
213    /// 
214    /// // Process file page by page
215    /// for page in mmap.pages() {
216    ///     let data = page?;
217    ///     // Process page...
218    /// }
219    /// # Ok::<(), mmap_io::MmapIoError>(())
220    /// ```
221    #[cfg(feature = "iterator")]
222    pub fn pages(&self) -> PageIterator<'_> {
223        PageIterator::new(self).expect("page iterator creation should not fail")
224    }
225
226    /// Create a mutable iterator over fixed-size chunks of the file.
227    ///
228    /// This is only available for read-write mappings. Due to Rust's borrowing rules,
229    /// this returns an iterator that processes chunks through a callback.
230    ///
231    /// # Arguments
232    ///
233    /// * `chunk_size` - Size of each chunk in bytes
234    ///
235    /// # Examples
236    ///
237    /// ```no_run
238    /// use mmap_io::{MemoryMappedFile, MmapMode};
239    ///
240    /// let mmap = MemoryMappedFile::open_rw("data.bin")?;
241    /// 
242    /// // Zero out file in 4KB chunks
243    /// mmap.chunks_mut(4096).for_each_mut(|offset, chunk| {
244    ///     chunk.fill(0);
245    ///     Ok::<(), std::io::Error>(())
246    /// })??;
247    /// # Ok::<(), mmap_io::MmapIoError>(())
248    /// ```
249    #[cfg(feature = "iterator")]
250    pub fn chunks_mut(&self, chunk_size: usize) -> ChunkIteratorMut<'_> {
251        ChunkIteratorMut::new(self, chunk_size).expect("mutable chunk iterator creation should not fail")
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258    use crate::create_mmap;
259    use std::fs;
260    use std::path::PathBuf;
261
262    fn tmp_path(name: &str) -> PathBuf {
263        let mut p = std::env::temp_dir();
264        p.push(format!("mmap_io_iterator_test_{}_{}", name, std::process::id()));
265        p
266    }
267
268    #[test]
269    #[cfg(feature = "iterator")]
270    fn test_chunk_iterator() {
271        let path = tmp_path("chunk_iter");
272        let _ = fs::remove_file(&path);
273
274        // Create file with pattern
275        let mmap = create_mmap(&path, 10240).expect("create");
276        for i in 0..10 {
277            let data = vec![i as u8; 1024];
278            mmap.update_region(i * 1024, &data).expect("write");
279        }
280        mmap.flush().expect("flush");
281
282        // Test chunk iteration
283        let chunks: Vec<_> = mmap.chunks(1024)
284            .collect::<Result<Vec<_>>>()
285            .expect("collect chunks");
286        
287        assert_eq!(chunks.len(), 10);
288        for (i, chunk) in chunks.iter().enumerate() {
289            assert_eq!(chunk.len(), 1024);
290            assert!(chunk.iter().all(|&b| b == i as u8));
291        }
292
293        // Test with non-aligned chunk size
294        let chunks: Vec<_> = mmap.chunks(3000)
295            .collect::<Result<Vec<_>>>()
296            .expect("collect chunks");
297        
298        assert_eq!(chunks.len(), 4); // 3000, 3000, 3000, 1240
299        assert_eq!(chunks[3].len(), 1240);
300
301        fs::remove_file(&path).expect("cleanup");
302    }
303
304    #[test]
305    #[cfg(feature = "iterator")]
306    fn test_page_iterator() {
307        let path = tmp_path("page_iter");
308        let _ = fs::remove_file(&path);
309
310        let ps = page_size();
311        let file_size = ps * 3 + 100; // 3 full pages + partial
312
313        let mmap = create_mmap(&path, file_size as u64).expect("create");
314        
315        let pages: Vec<_> = mmap.pages()
316            .collect::<Result<Vec<_>>>()
317            .expect("collect pages");
318        
319        assert_eq!(pages.len(), 4); // 3 full + 1 partial
320        assert_eq!(pages[0].len(), ps);
321        assert_eq!(pages[1].len(), ps);
322        assert_eq!(pages[2].len(), ps);
323        assert_eq!(pages[3].len(), 100);
324
325        fs::remove_file(&path).expect("cleanup");
326    }
327
328    #[test]
329    #[cfg(feature = "iterator")]
330    fn test_mutable_chunk_iterator() {
331        let path = tmp_path("mut_chunk_iter");
332        let _ = fs::remove_file(&path);
333
334        let mmap = create_mmap(&path, 4096).expect("create");
335        
336        // Fill chunks with different values
337        let result = mmap.chunks_mut(1024).for_each_mut(|offset, chunk| {
338            let value = (offset / 1024) as u8;
339            chunk.fill(value);
340            Ok::<(), std::io::Error>(())
341        });
342        
343        assert!(result.is_ok());
344        assert!(result.unwrap().is_ok());
345        
346        mmap.flush().expect("flush");
347
348        // Verify
349        let mut buf = [0u8; 1024];
350        for i in 0..4 {
351            mmap.read_into(i * 1024, &mut buf).expect("read");
352            assert!(buf.iter().all(|&b| b == i as u8));
353        }
354
355        fs::remove_file(&path).expect("cleanup");
356    }
357
358    #[test]
359    #[cfg(feature = "iterator")]
360    fn test_iterator_size_hint() {
361        let path = tmp_path("size_hint");
362        let _ = fs::remove_file(&path);
363
364        let mmap = create_mmap(&path, 10000).expect("create");
365        
366        let iter = mmap.chunks(1000);
367        assert_eq!(iter.size_hint(), (10, Some(10)));
368        
369        let iter = mmap.chunks(3000);
370        assert_eq!(iter.size_hint(), (4, Some(4)));
371
372        fs::remove_file(&path).expect("cleanup");
373    }
374
375    #[test]
376    #[cfg(feature = "iterator")]
377    fn test_empty_file_iteration() {
378        let path = tmp_path("empty_iter");
379        let _ = fs::remove_file(&path);
380
381        let mmap = create_mmap(&path, 1).expect("create"); // Can't create 0-size
382        mmap.resize(1).expect("resize"); // Keep it minimal
383        
384        let chunks: Vec<_> = mmap.chunks(1024)
385            .collect::<Result<Vec<_>>>()
386            .expect("collect");
387        
388        assert_eq!(chunks.len(), 1);
389        assert_eq!(chunks[0].len(), 1);
390
391        fs::remove_file(&path).expect("cleanup");
392    }
393}