mmap_io/iterator.rs
1//! Zero-copy iterator-based access to a memory-mapped file.
2//!
3//! [`ChunkIterator`] and [`PageIterator`] yield [`MappedSlice<'a>`]
4//! items that borrow directly from the underlying mapping (no
5//! allocation, no copy). On RW mappings the iterator holds a read
6//! guard for its entire lifetime, which blocks any concurrent
7//! `resize()` until iteration completes.
8//!
9//! The owned variants ([`ChunkIteratorOwned`], [`PageIteratorOwned`])
10//! yield `Result<Vec<u8>>` for callers that genuinely need owned
11//! buffers (e.g. when handing data to a thread that outlives the
12//! mapping borrow). They allocate one `Vec<u8>` per item.
13
14use crate::errors::{MmapIoError, Result};
15use crate::mmap::{MapVariant, MappedSlice, MemoryMappedFile};
16use crate::utils::page_size;
17use memmap2::MmapMut;
18use parking_lot::RwLockReadGuard;
19use std::marker::PhantomData;
20
21/// Internal guard variant: holds the RW read lock alive (so the
22/// underlying mapping cannot be remapped via `resize()`), or is
23/// `None` for RO / COW mappings whose mappings are inherently
24/// immutable.
25///
26/// The `Held` variant's field is never read; it exists for its
27/// destructor only (drops the read lock when the iterator is
28/// dropped).
29#[allow(dead_code)]
30enum IterGuard<'a> {
31 /// RW mapping: read guard kept alive for the iterator's life.
32 Held(RwLockReadGuard<'a, MmapMut>),
33 /// RO / COW mapping: no lock to hold.
34 None,
35}
36
37/// Iterator over fixed-size chunks of a memory-mapped file.
38///
39/// Yields [`MappedSlice<'a>`] items that borrow directly from the
40/// mapped region. The iterator holds the underlying read lock (on RW
41/// mappings) for its lifetime, so calls to `resize()` from another
42/// thread will block until the iterator is dropped.
43///
44/// # Examples
45///
46/// ```no_run
47/// use mmap_io::MemoryMappedFile;
48///
49/// let mmap = MemoryMappedFile::open_ro("data.bin")?;
50///
51/// // Iterate over 4 KiB chunks. Each `chunk` is `MappedSlice<'_>`,
52/// // which derefs to `&[u8]`.
53/// for (i, chunk) in mmap.chunks(4096).enumerate() {
54/// let _ = chunk.len();
55/// let _first_byte = chunk[0];
56/// println!("Chunk {} has {} bytes", i, chunk.len());
57/// }
58/// # Ok::<(), mmap_io::MmapIoError>(())
59/// ```
60pub struct ChunkIterator<'a> {
61 /// Base pointer to the mapped region. Valid for `'a` because the
62 /// guard (or the immutable underlying mapping for RO/COW) keeps
63 /// the address space stable.
64 base: *const u8,
65 /// Total bytes in the mapping. Captured at iterator construction
66 /// and not re-checked; the guard (RW) or immutable mapping
67 /// (RO/COW) ensures the length cannot change during iteration.
68 total_len: usize,
69 /// Bytes per yielded chunk. The final chunk may be shorter.
70 chunk_size: usize,
71 /// Offset into the mapped region of the next chunk to yield.
72 current_offset: usize,
73 /// Lifetime / unmap guard.
74 _guard: IterGuard<'a>,
75 /// Notional borrow tying the raw pointer to `'a`.
76 _marker: PhantomData<&'a [u8]>,
77}
78
79// SAFETY: ChunkIterator is `Send` because:
80// - For `Held`, parking_lot's `RwLockReadGuard` is `Send` and `Sync`.
81// - For `None`, no lock is held; the pointer targets an immutable
82// mapping that lives at least as long as `'a`.
83// - The pointer itself targets `u8`, which is `Send + Sync`.
84// The iterator yields immutable slices, so multiple yielded items
85// can coexist as standard shared borrows.
86unsafe impl<'a> Send for ChunkIterator<'a> {}
87// SAFETY: same justification as `Send`; sharing `&ChunkIterator`
88// across threads only allows calling `next()` from one thread at a
89// time (Iterator's contract requires `&mut self`), and the read-only
90// access pattern matches.
91unsafe impl<'a> Sync for ChunkIterator<'a> {}
92
93impl<'a> ChunkIterator<'a> {
94 pub(crate) fn new(mmap: &'a MemoryMappedFile, chunk_size: usize) -> Result<Self> {
95 let total_len = usize::try_from(mmap.current_len()?)
96 .map_err(|_| MmapIoError::ResizeFailed("mapping length exceeds usize::MAX".into()))?;
97
98 let (base, guard) = match &mmap.inner.map {
99 MapVariant::Ro(m) => (m.as_ptr(), IterGuard::None),
100 MapVariant::Rw(lock) => {
101 let g = lock.read();
102 let ptr = g.as_ptr();
103 (ptr, IterGuard::Held(g))
104 }
105 MapVariant::Cow(m) => (m.as_ptr(), IterGuard::None),
106 };
107
108 Ok(Self {
109 base,
110 total_len,
111 chunk_size,
112 current_offset: 0,
113 _guard: guard,
114 _marker: PhantomData,
115 })
116 }
117}
118
119impl<'a> Iterator for ChunkIterator<'a> {
120 type Item = MappedSlice<'a>;
121
122 fn next(&mut self) -> Option<Self::Item> {
123 if self.chunk_size == 0 || self.current_offset >= self.total_len {
124 return None;
125 }
126 let remaining = self.total_len - self.current_offset;
127 let chunk_len = remaining.min(self.chunk_size);
128
129 // SAFETY: `base.add(current_offset)` produces a pointer
130 // inside the mapped region because `current_offset <
131 // total_len <= mapping length` (the mapping is stable for
132 // `'a` per the guard / immutable variant in construction).
133 // `slice::from_raw_parts` with `chunk_len <= remaining`
134 // produces a slice that does not escape the mapped region.
135 // The resulting `&'a [u8]` shares the lifetime of the guard
136 // (`'a`), so multiple yielded chunks can coexist as
137 // immutable borrows. No mutation of the mapping is possible
138 // while the guard is alive (write lock would be required and
139 // is blocked by the held read guard).
140 let slice: &'a [u8] =
141 unsafe { std::slice::from_raw_parts(self.base.add(self.current_offset), chunk_len) };
142 self.current_offset += chunk_len;
143 Some(MappedSlice::owned(slice))
144 }
145
146 fn size_hint(&self) -> (usize, Option<usize>) {
147 if self.chunk_size == 0 {
148 return (0, Some(0));
149 }
150 let remaining = self.total_len.saturating_sub(self.current_offset);
151 let chunks = remaining.div_ceil(self.chunk_size);
152 (chunks, Some(chunks))
153 }
154}
155
156impl<'a> ExactSizeIterator for ChunkIterator<'a> {}
157
158/// Iterator over page-aligned chunks of a memory-mapped file.
159///
160/// Equivalent to `mmap.chunks(page_size())` with the same zero-copy
161/// guarantees.
162///
163/// # Examples
164///
165/// ```no_run
166/// use mmap_io::MemoryMappedFile;
167///
168/// let mmap = MemoryMappedFile::open_ro("data.bin")?;
169/// for page in mmap.pages() {
170/// let _ = page.len();
171/// }
172/// # Ok::<(), mmap_io::MmapIoError>(())
173/// ```
174pub struct PageIterator<'a> {
175 inner: ChunkIterator<'a>,
176}
177
178impl<'a> PageIterator<'a> {
179 pub(crate) fn new(mmap: &'a MemoryMappedFile) -> Result<Self> {
180 let ps = page_size();
181 Ok(Self {
182 inner: ChunkIterator::new(mmap, ps)?,
183 })
184 }
185}
186
187impl<'a> Iterator for PageIterator<'a> {
188 type Item = MappedSlice<'a>;
189
190 fn next(&mut self) -> Option<Self::Item> {
191 self.inner.next()
192 }
193
194 fn size_hint(&self) -> (usize, Option<usize>) {
195 self.inner.size_hint()
196 }
197}
198
199impl<'a> ExactSizeIterator for PageIterator<'a> {}
200
201/// Migration-aid iterator that yields owned `Vec<u8>` chunks. Each
202/// chunk is allocated and copied from the mapping. Prefer
203/// [`ChunkIterator`] (via `chunks()`) for zero-copy access.
204///
205/// # Examples
206///
207/// ```no_run
208/// use mmap_io::MemoryMappedFile;
209///
210/// let mmap = MemoryMappedFile::open_ro("data.bin")?;
211/// for chunk in mmap.chunks_owned(4096) {
212/// let bytes: Vec<u8> = chunk?;
213/// let _ = bytes;
214/// }
215/// # Ok::<(), mmap_io::MmapIoError>(())
216/// ```
217pub struct ChunkIteratorOwned<'a> {
218 inner: ChunkIterator<'a>,
219}
220
221impl<'a> ChunkIteratorOwned<'a> {
222 pub(crate) fn new(mmap: &'a MemoryMappedFile, chunk_size: usize) -> Result<Self> {
223 Ok(Self {
224 inner: ChunkIterator::new(mmap, chunk_size)?,
225 })
226 }
227}
228
229impl<'a> Iterator for ChunkIteratorOwned<'a> {
230 type Item = Result<Vec<u8>>;
231
232 fn next(&mut self) -> Option<Self::Item> {
233 self.inner.next().map(|slice| Ok(slice.as_slice().to_vec()))
234 }
235
236 fn size_hint(&self) -> (usize, Option<usize>) {
237 self.inner.size_hint()
238 }
239}
240
241impl<'a> ExactSizeIterator for ChunkIteratorOwned<'a> {}
242
243/// Migration-aid iterator that yields owned page-sized `Vec<u8>`
244/// buffers. Prefer [`PageIterator`] (via `pages()`) for zero-copy.
245pub struct PageIteratorOwned<'a> {
246 inner: PageIterator<'a>,
247}
248
249impl<'a> PageIteratorOwned<'a> {
250 pub(crate) fn new(mmap: &'a MemoryMappedFile) -> Result<Self> {
251 Ok(Self {
252 inner: PageIterator::new(mmap)?,
253 })
254 }
255}
256
257impl<'a> Iterator for PageIteratorOwned<'a> {
258 type Item = Result<Vec<u8>>;
259
260 fn next(&mut self) -> Option<Self::Item> {
261 self.inner.next().map(|slice| Ok(slice.as_slice().to_vec()))
262 }
263
264 fn size_hint(&self) -> (usize, Option<usize>) {
265 self.inner.size_hint()
266 }
267}
268
269impl<'a> ExactSizeIterator for PageIteratorOwned<'a> {}
270
271/// Mutable chunk iterator: callback-based because Rust's borrow
272/// checker does not allow yielding multiple mutable references from
273/// one iterator. The iterator acquires the underlying RW write lock
274/// once and holds it for the entire iteration, then drives the
275/// caller's closure on each chunk in order.
276pub struct ChunkIteratorMut<'a> {
277 mmap: &'a MemoryMappedFile,
278 chunk_size: usize,
279 total_len: u64,
280 _phantom: PhantomData<&'a mut [u8]>,
281}
282
283impl<'a> ChunkIteratorMut<'a> {
284 pub(crate) fn new(mmap: &'a MemoryMappedFile, chunk_size: usize) -> Result<Self> {
285 let total_len = mmap.current_len()?;
286 Ok(Self {
287 mmap,
288 chunk_size,
289 total_len,
290 _phantom: PhantomData,
291 })
292 }
293
294 /// Process each chunk under a single held write guard. The
295 /// closure receives `(offset, &mut [u8])` for each chunk in
296 /// order. Returning `Err` aborts iteration and surfaces the
297 /// error to the caller.
298 ///
299 /// The closure's error type is the crate's [`MmapIoError`].
300 /// Callers carrying a foreign error type should map into
301 /// `MmapIoError` before returning (e.g. via `.map_err(|e|
302 /// MmapIoError::Io(...))`).
303 ///
304 /// # Errors
305 ///
306 /// Returns [`MmapIoError::InvalidMode`] on read-only or COW
307 /// mappings (mutable iteration requires `ReadWrite`). Returns any
308 /// error propagated from the user closure.
309 pub fn for_each_mut<F>(self, mut f: F) -> Result<()>
310 where
311 F: FnMut(u64, &mut [u8]) -> Result<()>,
312 {
313 if self.chunk_size == 0 || self.total_len == 0 {
314 return Ok(());
315 }
316 match &self.mmap.inner.map {
317 MapVariant::Ro(_) => Err(MmapIoError::InvalidMode(
318 "chunks_mut requires ReadWrite mode",
319 )),
320 MapVariant::Cow(_) => Err(MmapIoError::InvalidMode(
321 "chunks_mut on copy-on-write mapping is not supported (phase-1 read-only)",
322 )),
323 MapVariant::Rw(lock) => {
324 let mut guard = lock.write();
325 let total = self.total_len as usize;
326 let chunk_size = self.chunk_size;
327 let mut offset = 0usize;
328 while offset < total {
329 let remaining = total - offset;
330 let chunk_len = remaining.min(chunk_size);
331 let end = offset + chunk_len;
332 f(offset as u64, &mut guard[offset..end])?;
333 offset = end;
334 }
335 Ok(())
336 }
337 }
338 }
339
340 /// Migration shim that mirrors the 0.9.6 `for_each_mut`
341 /// signature: returns `Result<std::result::Result<(), E>>`
342 /// where `E` is the closure's error type.
343 ///
344 /// **Prefer [`for_each_mut`](Self::for_each_mut)** for new
345 /// code; that method was flattened to `Result<()>` (using
346 /// the crate's `MmapIoError`) in 0.9.7. Foreign error types
347 /// should be mapped via `.map_err(|e| MmapIoError::Io(...))`
348 /// before returning.
349 ///
350 /// This shim exists for callers migrating off the 0.9.6
351 /// signature. Internally it still uses the new single-held-
352 /// guard implementation (the H2 perf win is preserved); only
353 /// the return shape is back-compat.
354 ///
355 /// # Errors
356 ///
357 /// Returns the outer `Err(MmapIoError)` for any mmap-side
358 /// failure (e.g. RW lock unavailable, OOB chunk during a
359 /// concurrent resize). Returns `Ok(Err(E))` for closure
360 /// errors. Returns `Ok(Ok(()))` when iteration completes
361 /// cleanly.
362 pub fn for_each_mut_legacy<F, E>(self, mut f: F) -> Result<std::result::Result<(), E>>
363 where
364 F: FnMut(u64, &mut [u8]) -> std::result::Result<(), E>,
365 {
366 if self.chunk_size == 0 || self.total_len == 0 {
367 return Ok(Ok(()));
368 }
369 match &self.mmap.inner.map {
370 MapVariant::Ro(_) => Err(MmapIoError::InvalidMode(
371 "chunks_mut requires ReadWrite mode",
372 )),
373 MapVariant::Cow(_) => Err(MmapIoError::InvalidMode(
374 "chunks_mut on copy-on-write mapping is not supported (phase-1 read-only)",
375 )),
376 MapVariant::Rw(lock) => {
377 let mut guard = lock.write();
378 let total = self.total_len as usize;
379 let chunk_size = self.chunk_size;
380 let mut offset = 0usize;
381 while offset < total {
382 let remaining = total - offset;
383 let chunk_len = remaining.min(chunk_size);
384 let end = offset + chunk_len;
385 match f(offset as u64, &mut guard[offset..end]) {
386 Ok(()) => offset = end,
387 Err(e) => return Ok(Err(e)),
388 }
389 }
390 Ok(Ok(()))
391 }
392 }
393 }
394}
395
396impl MemoryMappedFile {
397 /// Zero-copy chunk iterator. Yields [`MappedSlice<'_>`] of size
398 /// `chunk_size` (final chunk may be shorter).
399 ///
400 /// For RW mappings, the iterator holds a read guard for its
401 /// lifetime; concurrent `resize()` blocks until the iterator is
402 /// dropped.
403 ///
404 /// # Examples
405 ///
406 /// ```no_run
407 /// use mmap_io::MemoryMappedFile;
408 /// let mmap = MemoryMappedFile::open_ro("data.bin")?;
409 /// for chunk in mmap.chunks(64 * 1024) {
410 /// let _ = chunk.len();
411 /// }
412 /// # Ok::<(), mmap_io::MmapIoError>(())
413 /// ```
414 ///
415 /// # Panics
416 ///
417 /// Panics if iterator construction fails. This is unreachable for
418 /// supported inputs: the constructor's only failure mode is
419 /// `chunk_size == 0`, which the type-level contract documents as
420 /// invalid usage. Empty mappings produce an already-exhausted
421 /// iterator rather than an error.
422 #[cfg(feature = "iterator")]
423 #[must_use]
424 pub fn chunks(&self, chunk_size: usize) -> ChunkIterator<'_> {
425 ChunkIterator::new(self, chunk_size).expect("chunk iterator creation should not fail")
426 }
427
428 /// Zero-copy page-aligned iterator.
429 ///
430 /// # Panics
431 ///
432 /// Unreachable in practice; see [`chunks`](Self::chunks).
433 #[cfg(feature = "iterator")]
434 #[must_use]
435 pub fn pages(&self) -> PageIterator<'_> {
436 PageIterator::new(self).expect("page iterator creation should not fail")
437 }
438
439 /// Migration-aid: chunk iterator yielding owned `Vec<u8>` items.
440 /// Allocates one `Vec<u8>` per chunk and copies the data into it.
441 /// Prefer `chunks()` for zero-copy.
442 ///
443 /// # Panics
444 ///
445 /// Unreachable in practice; see [`chunks`](Self::chunks).
446 #[cfg(feature = "iterator")]
447 #[must_use]
448 pub fn chunks_owned(&self, chunk_size: usize) -> ChunkIteratorOwned<'_> {
449 ChunkIteratorOwned::new(self, chunk_size)
450 .expect("owned chunk iterator creation should not fail")
451 }
452
453 /// Migration-aid: page iterator yielding owned `Vec<u8>` items.
454 /// Prefer `pages()` for zero-copy.
455 ///
456 /// # Panics
457 ///
458 /// Unreachable in practice; see [`chunks`](Self::chunks).
459 #[cfg(feature = "iterator")]
460 #[must_use]
461 pub fn pages_owned(&self) -> PageIteratorOwned<'_> {
462 PageIteratorOwned::new(self).expect("owned page iterator creation should not fail")
463 }
464
465 /// Callback-driven mutable iterator. Acquires a single write
466 /// guard for the entire iteration. Available only on
467 /// `ReadWrite` mappings.
468 ///
469 /// # Examples
470 ///
471 /// ```no_run
472 /// use mmap_io::MemoryMappedFile;
473 ///
474 /// let mmap = MemoryMappedFile::open_rw("data.bin")?;
475 /// mmap.chunks_mut(4096).for_each_mut(|_offset, chunk| {
476 /// chunk.fill(0);
477 /// Ok(())
478 /// })?;
479 /// # Ok::<(), mmap_io::MmapIoError>(())
480 /// ```
481 ///
482 /// # Panics
483 ///
484 /// Unreachable in practice; see [`chunks`](Self::chunks).
485 #[cfg(feature = "iterator")]
486 #[must_use]
487 pub fn chunks_mut(&self, chunk_size: usize) -> ChunkIteratorMut<'_> {
488 ChunkIteratorMut::new(self, chunk_size)
489 .expect("mutable chunk iterator creation should not fail")
490 }
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496 use crate::create_mmap;
497 use std::fs;
498 use std::path::PathBuf;
499
500 fn tmp_path(name: &str) -> PathBuf {
501 let mut p = std::env::temp_dir();
502 p.push(format!(
503 "mmap_io_iterator_test_{}_{}",
504 name,
505 std::process::id()
506 ));
507 p
508 }
509
510 #[test]
511 #[cfg(feature = "iterator")]
512 fn test_chunk_iterator_zero_copy() {
513 let path = tmp_path("chunk_iter");
514 let _ = fs::remove_file(&path);
515
516 let mmap = create_mmap(&path, 10240).expect("create");
517 for i in 0..10 {
518 let data = vec![i as u8; 1024];
519 mmap.update_region(i * 1024, &data).expect("write");
520 }
521 mmap.flush().expect("flush");
522
523 // Aligned chunks: 10 x 1024.
524 let chunks: Vec<Vec<u8>> = mmap.chunks(1024).map(|s| s.as_slice().to_vec()).collect();
525 assert_eq!(chunks.len(), 10);
526 for (i, chunk) in chunks.iter().enumerate() {
527 assert_eq!(chunk.len(), 1024);
528 assert!(chunk.iter().all(|&b| b == i as u8));
529 }
530
531 // Unaligned chunks: 3000 / 3000 / 3000 / 1240.
532 let chunks: Vec<Vec<u8>> = mmap.chunks(3000).map(|s| s.as_slice().to_vec()).collect();
533 assert_eq!(chunks.len(), 4);
534 assert_eq!(chunks[3].len(), 1240);
535
536 drop(mmap);
537 fs::remove_file(&path).expect("cleanup");
538 }
539
540 #[test]
541 #[cfg(feature = "iterator")]
542 fn test_page_iterator_zero_copy() {
543 let path = tmp_path("page_iter");
544 let _ = fs::remove_file(&path);
545
546 let ps = page_size();
547 let file_size = ps * 3 + 100;
548
549 let mmap = create_mmap(&path, file_size as u64).expect("create");
550
551 let pages: Vec<usize> = mmap.pages().map(|p| p.len()).collect();
552 assert_eq!(pages.len(), 4);
553 assert_eq!(pages[0], ps);
554 assert_eq!(pages[1], ps);
555 assert_eq!(pages[2], ps);
556 assert_eq!(pages[3], 100);
557
558 drop(mmap);
559 fs::remove_file(&path).expect("cleanup");
560 }
561
562 #[test]
563 #[cfg(feature = "iterator")]
564 fn test_chunks_owned_compat() {
565 let path = tmp_path("chunks_owned");
566 let _ = fs::remove_file(&path);
567
568 let mmap = create_mmap(&path, 4096).expect("create");
569 mmap.update_region(0, &vec![0x11u8; 4096]).expect("write");
570
571 let owned: Vec<Vec<u8>> = mmap
572 .chunks_owned(1024)
573 .collect::<Result<Vec<_>>>()
574 .expect("collect");
575 assert_eq!(owned.len(), 4);
576 for v in &owned {
577 assert_eq!(v.len(), 1024);
578 assert!(v.iter().all(|&b| b == 0x11));
579 }
580
581 drop(mmap);
582 fs::remove_file(&path).expect("cleanup");
583 }
584
585 #[test]
586 #[cfg(feature = "iterator")]
587 fn test_mutable_chunk_iterator_single_guard() {
588 let path = tmp_path("mut_chunk_iter");
589 let _ = fs::remove_file(&path);
590
591 let mmap = create_mmap(&path, 4096).expect("create");
592
593 mmap.chunks_mut(1024)
594 .for_each_mut(|offset, chunk| {
595 let value = (offset / 1024) as u8;
596 chunk.fill(value);
597 Ok(())
598 })
599 .expect("for_each_mut");
600
601 mmap.flush().expect("flush");
602
603 let mut buf = [0u8; 1024];
604 for i in 0..4 {
605 mmap.read_into(i * 1024, &mut buf).expect("read");
606 assert!(buf.iter().all(|&b| b == i as u8));
607 }
608
609 drop(mmap);
610 fs::remove_file(&path).expect("cleanup");
611 }
612
613 #[test]
614 #[cfg(feature = "iterator")]
615 fn test_iterator_size_hint() {
616 let path = tmp_path("size_hint");
617 let _ = fs::remove_file(&path);
618
619 let mmap = create_mmap(&path, 10000).expect("create");
620
621 {
622 let iter = mmap.chunks(1000);
623 assert_eq!(iter.size_hint(), (10, Some(10)));
624 }
625 {
626 let iter = mmap.chunks(3000);
627 assert_eq!(iter.size_hint(), (4, Some(4)));
628 }
629
630 drop(mmap);
631 fs::remove_file(&path).expect("cleanup");
632 }
633
634 #[test]
635 #[cfg(feature = "iterator")]
636 fn test_iterator_zero_chunk_size_yields_nothing() {
637 let path = tmp_path("zero_chunk");
638 let _ = fs::remove_file(&path);
639
640 let mmap = create_mmap(&path, 4096).expect("create");
641 assert_eq!(mmap.chunks(0).count(), 0);
642
643 drop(mmap);
644 fs::remove_file(&path).expect("cleanup");
645 }
646
647 #[test]
648 #[cfg(feature = "iterator")]
649 fn test_one_byte_file_iteration() {
650 let path = tmp_path("one_byte_iter");
651 let _ = fs::remove_file(&path);
652
653 let mmap = create_mmap(&path, 1).expect("create");
654 let chunks: Vec<usize> = mmap.chunks(1024).map(|s| s.len()).collect();
655 assert_eq!(chunks.len(), 1);
656 assert_eq!(chunks[0], 1);
657
658 drop(mmap);
659 fs::remove_file(&path).expect("cleanup");
660 }
661}