From 0629bd19d763450d18444402d1c70cfdbdaab7af Mon Sep 17 00:00:00 2001 From: an_owl <32559552+an-owl@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:46:51 +1300 Subject: [PATCH 01/10] Added optimisations for `AddrIter` --- src/lib.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 41acb7e..6391b0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ use core::fmt; use core::fmt::Debug; +use core::iter::FusedIterator; use core::ops::{ Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign, @@ -148,10 +149,16 @@ pub struct AddrIter { current: T, end: T, } + +impl AddrIter { + fn exhausted(&self) -> bool { + self.current >= self.end + } +} impl Iterator for AddrIter { type Item = T; fn next(&mut self) -> Option { - if self.current >= self.end { + if self.exhausted() { None } else { let ret = Some(self.current); @@ -159,8 +166,62 @@ impl Iterator for AddrIter { ret } } + + fn count(self) -> usize + where + Self: Sized, + { + (self.end.raw() - self.current.raw()) + .try_into() + .expect("address range is larger than the architecture's usize") + } + + fn last(self) -> Option { + self.max() + } + + fn max(self) -> Option + where + Self: Sized, + Self::Item: Ord, + { + Some(self.end) + } + + fn min(self) -> Option { + Some(self.current) + } + + fn is_sorted(self) -> bool + where + Self: Sized, + Self::Item: PartialOrd, + { + true + } +} + +impl DoubleEndedIterator for AddrIter { + fn next_back(&mut self) -> Option { + if self.exhausted() { + None + } else { + self.end -= 1.into(); + Some(self.end) + } + } } +impl ExactSizeIterator for AddrIter { + fn len(&self) -> usize { + (self.end.raw() - self.current.raw()) + .try_into() + .expect("address range is larger than the architecture's usize") + } +} + +impl FusedIterator for AddrIter {} + #[cfg(test)] mod tests { use super::*; @@ -194,6 +255,21 @@ mod tests { assert_eq!(a.raw() as usize, i); } + { + let mut range = r.iter(); + assert_eq!(range.next_back(), Some(VirtAddr::new(0x2))); + assert_eq!(range.next_back(), Some(VirtAddr::new(0x1))); + assert_eq!(range.next_back(), Some(VirtAddr::new(0x0))); + assert_eq!(range.next_back(), None); + assert_eq!(range.next(), None); + + let mut range = r.iter(); + assert_eq!(range.next(), Some(VirtAddr::new(0x0))); + assert_eq!(range.next_back(), Some(VirtAddr::new(0x2))); + assert_eq!(range.next(), Some(VirtAddr::new(0x1))); + assert_eq!(range.next_back(), None); + } + let r = AddrRange::new(PhysAddr::new(0x2), PhysAddr::new(0x4)).unwrap(); let mut i = r.iter(); assert_eq!(i.next().unwrap(), PhysAddr::new(0x2)); From c5db2b245b84a5cd13ca732e465bbea3fa65f64b Mon Sep 17 00:00:00 2001 From: an_owl <32559552+an-owl@users.noreply.github.com> Date: Thu, 13 Nov 2025 14:50:32 +1300 Subject: [PATCH 02/10] Added inclusive iterator --- src/lib.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 113 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6391b0e..ec23b31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ use core::fmt; use core::fmt::Debug; use core::iter::FusedIterator; +use core::marker::PhantomData; use core::ops::{ Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign, @@ -125,7 +126,8 @@ impl AddrRange { pub fn iter(&self) -> AddrIter { AddrIter { current: self.start, - end: self.end, + end: Some(self.end), + _phantom: PhantomData, } } @@ -145,20 +147,36 @@ impl AddrRange { } /// An iterator over a memory range -pub struct AddrIter { +#[allow(private_bounds)] +pub struct AddrIter { current: T, - end: T, + end: Option, // None here indicates that this is exhausted + _phantom: PhantomData, } -impl AddrIter { - fn exhausted(&self) -> bool { - self.current >= self.end +trait IterInclusivity { + fn exhausted(start: &T, end: &T) -> bool; +} +pub enum NonInclusive {} + +impl IterInclusivity for NonInclusive { + fn exhausted(start: &T, end: &T) -> bool { + start >= end } } -impl Iterator for AddrIter { + +pub enum Inclusive {} + +impl IterInclusivity for Inclusive { + fn exhausted(start: &T, end: &T) -> bool { + start > end + } +} + +impl Iterator for AddrIter { type Item = T; fn next(&mut self) -> Option { - if self.exhausted() { + if I::exhausted(&self.current, &self.end?) { None } else { let ret = Some(self.current); @@ -171,7 +189,8 @@ impl Iterator for AddrIter { where Self: Sized, { - (self.end.raw() - self.current.raw()) + let Some(end) = self.end else { return 0 }; + (end.raw() - self.current.raw()) .try_into() .expect("address range is larger than the architecture's usize") } @@ -185,7 +204,7 @@ impl Iterator for AddrIter { Self: Sized, Self::Item: Ord, { - Some(self.end) + Some(self.end.unwrap_or(self.current)) } fn min(self) -> Option { @@ -201,20 +220,54 @@ impl Iterator for AddrIter { } } -impl DoubleEndedIterator for AddrIter { +impl DoubleEndedIterator for AddrIter { fn next_back(&mut self) -> Option { - if self.exhausted() { + if NonInclusive::exhausted(&self.current, &self.end?) { None } else { - self.end -= 1.into(); - Some(self.end) + let one: T::RAW = 1u8.into(); + self.end = Some(self.end? - one); + self.end } } } -impl ExactSizeIterator for AddrIter { +impl DoubleEndedIterator for AddrIter { + fn next_back(&mut self) -> Option { + if Inclusive::exhausted(&self.current, &self.end?) { + None + } else { + let ret = self.end?; + + // We need to be able to step back to `0`. + // We return `0` when self.end is currently `0`. + // But then we subtract `0` by `1` triggering a sub-with-overflow + // When we trigger a sub-with-overflow we return early and dont decrement `self.end` + // The next call to self.next() will return as exhausted and the + let Some(step) = self.end?.checked_sub(1.into()) else { + self.end = None; + return Some(ret); + }; + self.end = Some(step); + Some(ret) + } + } +} + +impl ExactSizeIterator for AddrIter { + fn len(&self) -> usize { + let Some(end) = self.end else { return 0 }; + (end - self.current) + .try_into() + .expect("address range is larger than the architecture's usize") + + 1 + } +} + +impl ExactSizeIterator for AddrIter { fn len(&self) -> usize { - (self.end.raw() - self.current.raw()) + let Some(end) = self.end else { return 0 }; + (end - self.current) .try_into() .expect("address range is larger than the architecture's usize") } @@ -222,6 +275,26 @@ impl ExactSizeIterator for AddrIter { impl FusedIterator for AddrIter {} +impl From> for AddrIter { + fn from(range: core::ops::Range) -> Self { + Self { + current: range.start, + end: Some(range.end), + _phantom: PhantomData, + } + } +} + +impl From> for AddrIter { + fn from(range: core::ops::RangeInclusive) -> Self { + Self { + current: *range.start(), + end: Some(*range.end()), + _phantom: PhantomData, + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -278,4 +351,28 @@ mod tests { assert_eq!(r.iter().map(|a| a.raw() as usize).sum::(), 0x5); } + + #[test] + fn test_iter_incl() { + let range = VirtAddr::new(0x0)..=VirtAddr::new(0x3); + let mut i = AddrIter::from(range.clone()); + assert_eq!(i.next().unwrap(), VirtAddr::new(0x0)); + assert_eq!(i.next().unwrap(), VirtAddr::new(0x1)); + assert_eq!(i.next().unwrap(), VirtAddr::new(0x2)); + assert_eq!(i.next().unwrap(), VirtAddr::new(0x3)); + + let mut i = AddrIter::from(range.clone()); + assert_eq!(i.next_back(), Some(VirtAddr::new(0x3))); + assert_eq!(i.next_back(), Some(VirtAddr::new(0x2))); + assert_eq!(i.next_back(), Some(VirtAddr::new(0x1))); + assert_eq!(i.next_back(), Some(VirtAddr::new(0x0))); + assert_eq!(i.next_back(), None); + + let mut i = AddrIter::from(range.clone()); + assert_eq!(i.next_back(), Some(VirtAddr::new(0x3))); + assert_eq!(i.next(), Some(VirtAddr::new(0x0))); + assert_eq!(i.next_back(), Some(VirtAddr::new(0x2))); + assert_eq!(i.next(), Some(VirtAddr::new(0x1))); + assert_eq!(i.next_back(), None); + } } From 7d0c3da3b15bc5a29287e8f572abe1070a3b5845 Mon Sep 17 00:00:00 2001 From: an_owl <32559552+an-owl@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:29:44 +1300 Subject: [PATCH 03/10] Fixed logic error where `size_hint()` is not implemented but is required by `ExactSizeIterator` --- src/lib.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index ec23b31..0260b75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,7 +154,7 @@ pub struct AddrIter { _phantom: PhantomData, } -trait IterInclusivity { +trait IterInclusivity: 'static { fn exhausted(start: &T, end: &T) -> bool; } pub enum NonInclusive {} @@ -185,6 +185,22 @@ impl Iterator for AddrIter { } } + fn size_hint(&self) -> (usize, Option) { + let Some(end) = self.end else { + return (0, Some(0)); + }; + let ni_count = (end - self.current) + .try_into() + .expect("address range is larger than the architecture's usize"); + if core::any::TypeId::of::() == core::any::TypeId::of::() { + (ni_count, Some(ni_count)) + } else if core::any::TypeId::of::() == core::any::TypeId::of::() { + (ni_count + 1, Some(ni_count + 1)) + } else { + unreachable!() + } + } + fn count(self) -> usize where Self: Sized, From 8637ae643200be14a2d6d1512cd125dc2af2d734 Mon Sep 17 00:00:00 2001 From: an_owl <32559552+an-owl@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:33:44 +1300 Subject: [PATCH 04/10] Implemented methods which require `MemoryAddress::RAW` to be `TryFrom` --- src/lib.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0260b75..c01d6ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,8 @@ pub trait MemoryAddress: + BitXor + Debug + From - + TryInto; + + TryInto + + TryFrom; /// Get the raw underlying address value. fn raw(self) -> Self::RAW; @@ -201,20 +202,23 @@ impl Iterator for AddrIter { } } - fn count(self) -> usize - where - Self: Sized, - { - let Some(end) = self.end else { return 0 }; - (end.raw() - self.current.raw()) - .try_into() - .expect("address range is larger than the architecture's usize") - } - fn last(self) -> Option { self.max() } + fn nth(&mut self, n: usize) -> Option { + let Ok(n): Result = n.try_into() else { + // Fail to cast indicates that n > T::RAW::MAX, so we explicitly exhaust self. + self.end.take(); + return None; + }; + self.current += n; + if I::exhausted(&self.current, &self.end?) { + return None; + } + Some(self.current) + } + fn max(self) -> Option where Self: Sized, @@ -246,6 +250,22 @@ impl DoubleEndedIterator for AddrIter { self.end } } + fn nth_back(&mut self, n: usize) -> Option { + if n == 0 { + return self.next_back(); // Avoids sub-with-overflow below + } + let Ok(n): Result = n.try_into() else { + // Fail to cast indicates that n > T::RAW::MAX, so we explicitly exhaust self. + self.end.take(); + return None; + }; + let Some(ret) = self.end?.checked_sub(n) else { + self.end.take(); + return None; + }; + self.end = Some(ret); + self.next_back() + } } impl DoubleEndedIterator for AddrIter { @@ -268,6 +288,24 @@ impl DoubleEndedIterator for AddrIter { Some(ret) } } + + fn nth_back(&mut self, n: usize) -> Option { + if n == 0 { + return self.next_back(); + } + let Ok(n): Result = n.try_into() else { + // Fail to cast indicates that n > T::RAW::MAX, so we explicitly exhaust self. + self.end.take(); + return None; + }; + + let Some(ret) = self.end?.checked_sub(n) else { + self.end.take(); + return None; + }; + self.end = Some(ret); + self.end + } } impl ExactSizeIterator for AddrIter { @@ -344,6 +382,11 @@ mod tests { assert_eq!(a.raw() as usize, i); } + assert_eq!(r.iter().nth(0), Some(VirtAddr::new(0x0))); + assert_eq!(r.iter().nth(1), Some(VirtAddr::new(0x1))); + assert_eq!(r.iter().nth(2), Some(VirtAddr::new(0x2))); + assert_eq!(r.iter().nth(3), None); + { let mut range = r.iter(); assert_eq!(range.next_back(), Some(VirtAddr::new(0x2))); @@ -357,6 +400,11 @@ mod tests { assert_eq!(range.next_back(), Some(VirtAddr::new(0x2))); assert_eq!(range.next(), Some(VirtAddr::new(0x1))); assert_eq!(range.next_back(), None); + + assert_eq!(r.iter().nth_back(0), Some(VirtAddr::new(0x2))); + assert_eq!(r.iter().nth_back(1), Some(VirtAddr::new(0x1))); + assert_eq!(r.iter().nth_back(2), Some(VirtAddr::new(0x0))); + assert_eq!(r.iter().nth_back(3), None); } let r = AddrRange::new(PhysAddr::new(0x2), PhysAddr::new(0x4)).unwrap(); @@ -390,5 +438,40 @@ mod tests { assert_eq!(i.next_back(), Some(VirtAddr::new(0x2))); assert_eq!(i.next(), Some(VirtAddr::new(0x1))); assert_eq!(i.next_back(), None); + + assert_eq!( + AddrIter::from(range.clone()).nth(0), + Some(VirtAddr::new(0x0)) + ); + assert_eq!( + AddrIter::from(range.clone()).nth(1), + Some(VirtAddr::new(0x1)) + ); + assert_eq!( + AddrIter::from(range.clone()).nth(2), + Some(VirtAddr::new(0x2)) + ); + assert_eq!( + AddrIter::from(range.clone()).nth(3), + Some(VirtAddr::new(0x3)) + ); + assert_eq!(AddrIter::from(range.clone()).nth(4), None); + } + + #[test] + fn iterator_assert_sizes() { + let range_incl = VirtAddr::new(0x0)..=VirtAddr::new(0x3); + assert_eq!( + AddrIter::from(range_incl.clone()).count(), + AddrIter::from(range_incl.clone()).len() + ); + assert_eq!( + AddrIter::from(range_incl.clone()).count(), + AddrIter::from(range_incl.clone()).size_hint().0 + ); + assert_eq!( + AddrIter::from(range_incl.clone()).count(), + AddrIter::from(range_incl.clone()).size_hint().1.unwrap() + ); } } From f107dc9a3a8af7cc070551ef18526b72d34080e7 Mon Sep 17 00:00:00 2001 From: an_owl <32559552+an-owl@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:06:46 +1300 Subject: [PATCH 05/10] Fixed bug where DoubleEndedIterator did not panic on a non-canonical address. --- src/lib.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c01d6ec..0e02735 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -212,7 +212,14 @@ impl Iterator for AddrIter { self.end.take(); return None; }; - self.current += n; + match self.current.checked_add(n) { + Some(n) => self.current = n, + None if self.current.raw() < n => { + self.end.take(); + return None; + } + None => panic!("Attempted to iterate over invalid address"), + } if I::exhausted(&self.current, &self.end?) { return None; } @@ -260,6 +267,9 @@ impl DoubleEndedIterator for AddrIter { return None; }; let Some(ret) = self.end?.checked_sub(n) else { + if self.end?.raw() < n { + panic!("Attempted to iterate over invalid address") + } self.end.take(); return None; }; @@ -281,6 +291,12 @@ impl DoubleEndedIterator for AddrIter { // When we trigger a sub-with-overflow we return early and dont decrement `self.end` // The next call to self.next() will return as exhausted and the let Some(step) = self.end?.checked_sub(1.into()) else { + // Check if this was an underflow or a non-canonical address + // Panic on non-canonical + // We can eat the overhead here because this branch is rare + if self.end?.raw() != 0u8.into() { + panic!("Attempted to iterate over invalid address") + } self.end = None; return Some(ret); }; @@ -300,6 +316,9 @@ impl DoubleEndedIterator for AddrIter { }; let Some(ret) = self.end?.checked_sub(n) else { + if self.end?.raw() < n { + panic!("Attempted to iterate over invalid address") + } self.end.take(); return None; }; From 0191a0957cc61e7d2919396f0ecc70bb34359361 Mon Sep 17 00:00:00 2001 From: an_owl <32559552+an-owl@users.noreply.github.com> Date: Mon, 17 Nov 2025 11:18:42 +1300 Subject: [PATCH 06/10] Added docs for `IterInclusivity` types --- src/lib.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0e02735..2030fb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -155,9 +155,26 @@ pub struct AddrIter { _phantom: PhantomData, } +// Note this is deliberately private. +// Users may need to know about its existence but do not need to implement or use it. trait IterInclusivity: 'static { fn exhausted(start: &T, end: &T) -> bool; } + +/// This marks [AddrIter] as as acting as non-inclusive. +/// +/// This is the behaviour when using [AddrRange::iter], it can also be constructed using [From]. +/// +///``` +/// # use memory_addresses::AddrIter; +/// let start = memory_addresses::PhysAddr::new(0); +/// let end = memory_addresses::PhysAddr::new(0x1000); +/// +/// for i in AddrIter::from(start..end) { +/// // ... +/// } +/// assert_eq!(AddrIter::from(start..end).last(), Some(memory_addresses::PhysAddr::new(0xfff))) +/// ``` pub enum NonInclusive {} impl IterInclusivity for NonInclusive { @@ -166,6 +183,20 @@ impl IterInclusivity for NonInclusive { } } +/// This marks [AddrIter] as as acting as inclusive. +/// +/// The inclusive variant of [AddrIter] can be constructed using [From]. +/// +///``` +/// # use memory_addresses::AddrIter; +/// let start = memory_addresses::PhysAddr::new(0); +/// let end = memory_addresses::PhysAddr::new(0x1000); +/// +/// for i in AddrIter::from(start..=end) { +/// // ... +/// } +/// assert_eq!(AddrIter::from(start..end).last(), Some(memory_addresses::PhysAddr::new(0x1000))) +/// ``` pub enum Inclusive {} impl IterInclusivity for Inclusive { From 47f9688205def3d9793f9606781524eb4f66d0ac Mon Sep 17 00:00:00 2001 From: an_owl <32559552+an-owl@users.noreply.github.com> Date: Mon, 17 Nov 2025 11:28:53 +1300 Subject: [PATCH 07/10] Added explanation for wierd type matching in `size_hint`. --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2030fb5..04423d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -224,11 +224,17 @@ impl Iterator for AddrIter { let ni_count = (end - self.current) .try_into() .expect("address range is larger than the architecture's usize"); + + // I whis there was a nicer way to do this. + // This will determine whether I is `Inclusive` or `NonInclusive` and take the correct branch. + // The compiler can determine which branch will be taken at compile time so this doesnt get checked at runtime. if core::any::TypeId::of::() == core::any::TypeId::of::() { (ni_count, Some(ni_count)) } else if core::any::TypeId::of::() == core::any::TypeId::of::() { (ni_count + 1, Some(ni_count + 1)) } else { + // Explicitly panic when I is not expected. + // This should never be possible but it doesnt do any harm. unreachable!() } } From 28f33993d18b3c9d63a7e98943a892ad788e2145 Mon Sep 17 00:00:00 2001 From: an_owl <32559552+an-owl@users.noreply.github.com> Date: Mon, 17 Nov 2025 11:38:12 +1300 Subject: [PATCH 08/10] Fixed bug where `max()` always returned the correct value for `I: Inclusive` --- src/lib.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 04423d6..93b322a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -268,7 +268,23 @@ impl Iterator for AddrIter { Self: Sized, Self::Item: Ord, { - Some(self.end.unwrap_or(self.current)) + let end = self.end?; + if core::any::TypeId::of::() == core::any::TypeId::of::() { + let Some(ret) = end.checked_sub(1.into()) else { + if end.raw() == 0u8.into() || end == self.current { + return None; // underflow (which is ok) or exhausted. + } else { + panic!("Attempted to iterate over invalid address") + } + }; + Some(ret) + } else if core::any::TypeId::of::() == core::any::TypeId::of::() { + Some(end) + } else { + // Explicitly panic when I is not expected. + // This should never be possible but it doesnt do any harm. + unreachable!() + } } fn min(self) -> Option { From 67637bbb2df613024c2a86025cd389a5a4066ebf Mon Sep 17 00:00:00 2001 From: an_owl <32559552+an-owl@users.noreply.github.com> Date: Mon, 17 Nov 2025 11:41:47 +1300 Subject: [PATCH 09/10] Foxed typo causing test to fail --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 93b322a..70fe8e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -195,7 +195,7 @@ impl IterInclusivity for NonInclusive { /// for i in AddrIter::from(start..=end) { /// // ... /// } -/// assert_eq!(AddrIter::from(start..end).last(), Some(memory_addresses::PhysAddr::new(0x1000))) +/// assert_eq!(AddrIter::from(start..=end).last(), Some(memory_addresses::PhysAddr::new(0x1000))) /// ``` pub enum Inclusive {} From 09b9e8a3e963415614ae5a1c36e28085ec89aac6 Mon Sep 17 00:00:00 2001 From: an_owl <32559552+an-owl@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:26:54 +1300 Subject: [PATCH 10/10] Cleared clippy warnings --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 70fe8e4..e3fd071 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -439,6 +439,7 @@ mod tests { } #[test] + #[allow(clippy::iter_nth_zero)] fn test_addr_range() { let r = AddrRange::new(VirtAddr::new(0x0), VirtAddr::new(0x3)).unwrap(); assert!(r.contains(&VirtAddr::new(0x0))); @@ -489,6 +490,7 @@ mod tests { } #[test] + #[allow(clippy::iter_nth_zero)] fn test_iter_incl() { let range = VirtAddr::new(0x0)..=VirtAddr::new(0x3); let mut i = AddrIter::from(range.clone());