diff --git a/Cargo.lock b/Cargo.lock index d32c4ee..ef96e41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,7 +288,7 @@ dependencies = [ [[package]] name = "iterator_ilp" -version = "2.0.6" +version = "2.1.0" dependencies = [ "criterion", "hwlocality", @@ -296,6 +296,7 @@ dependencies = [ "num-traits", "proptest", "rand", + "static_assertions", ] [[package]] @@ -602,6 +603,12 @@ dependencies = [ "serde", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index fabb19a..d355b1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ name = "iterator_ilp" # - Roll an annotated git tag # - Add a github release # -version = "2.0.6" +version = "2.1.0" authors = ["Hadrien G. "] edition = "2021" rust-version = "1.71.0" @@ -34,6 +34,7 @@ hwlocality = "1.0.0-alpha.3" multiversion = "0.7" proptest = "1.3" rand = "0.8" +static_assertions = "1.1.0" [[bench]] name = "benchmark" diff --git a/src/lib.rs b/src/lib.rs index aa84e06..aa5410d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -141,9 +141,10 @@ #![cfg_attr(not(any(test, feature = "std")), no_std)] #[cfg(doc)] -use core::iter::{FusedIterator, Product, Sum}; +use core::iter::{Product, Sum}; use core::{ cell::RefCell, + iter::FusedIterator, ops::{Add, Mul}, }; use num_traits::{One, Zero}; @@ -194,10 +195,19 @@ use num_traits::{One, Zero}; /// correct for safety. This is a subset of the contract of [`TrustedLen`], /// which, unfortunately, is unstable. /// -/// Therefore, we currently approximate it by providing our own version of -/// [`TrustedLen`] trait which is implemented for all standard library iterators -/// that implement [`TrustedLen`]. Once [`TrustedLen`] is stabilized, this crate -/// will blanket-impl [`TrustedLowerBound`] for it in a breaking release. +/// Therefore, we provide our own [`TrustedLowerBound`] unsafe trait, which we +/// implement for all standard library iterators. If you need to use +/// `iterator_ilp` with another iterator whose lower size bound you trust, you +/// can do either of the following: +/// +/// - Implement [`TrustedLowerBound`] for this iterator, if it's a type that you +/// control. This is the preferred path, because it allows users to leverage +/// `iterator_ilp` without unsafe assertions about types outside of their +/// control. In an ideal world, all numerical container libraries would +/// eventually provide such implementations. +/// - Use the [`AssertLowerBoundOk`] wrapper to unsafely assert, on your side, +/// that **you** trust an iterator to have a `size_hint()` implementation that +/// provides a correct lower bound. /// /// That's it for the general strategy, now to get into the detail of particular /// algorithms, we must divide [`Iterator`] reductions into three categories: @@ -881,12 +891,117 @@ mod core_iters { } } +/// Manual implementation of [`TrustedLowerBound`] for an iterator +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct AssertLowerBoundOk(I); +// +impl AssertLowerBoundOk { + /// Assert that the lower size bound provided by an iterator's `size_hint()` + /// method is correct. + /// + /// # Safety + /// + /// The lower size bound must indeed be correct. + #[inline] + pub unsafe fn new(inner: I) -> Self { + Self(inner) + } +} +// +impl DoubleEndedIterator for AssertLowerBoundOk { + #[inline] + fn next_back(&mut self) -> Option { + self.0.next_back() + } + + #[inline] + fn nth_back(&mut self, n: usize) -> Option { + self.0.nth_back(n) + } +} +// +impl ExactSizeIterator for AssertLowerBoundOk { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} +// +impl FusedIterator for AssertLowerBoundOk {} +// +impl Iterator for AssertLowerBoundOk { + type Item = I::Item; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + #[inline] + fn count(self) -> usize + where + I: Sized, + { + self.0.count() + } + + #[inline] + fn last(self) -> Option + where + I: Sized, + { + self.0.last() + } + + #[inline] + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n) + } +} +// +// # Safety +// +// Safety assertion is offloaded to the `new()` constructor +unsafe impl TrustedLowerBound for AssertLowerBoundOk {} + #[cfg(test)] mod tests { use super::*; use proptest::prelude::*; + use static_assertions::assert_impl_all; + + assert_impl_all!( + std::slice::Iter<'static, u32>: FusedIterator, TrustedLowerBound + ); proptest! { + #[test] + fn assert_lower_bound_basic(data: Vec) { + let raw = data.iter(); + // SAFETY: The size_hint of Vec's iterator is trusted + let iter = unsafe { AssertLowerBoundOk::new(raw.clone()) }; + assert_eq!(iter.size_hint(), raw.size_hint()); + assert_eq!(iter.len(), raw.len()); + assert_eq!(iter.clone().count(), raw.clone().count()); + assert_eq!(iter.clone().next(), raw.clone().next()); + assert_eq!(iter.clone().next_back(), raw.clone().next_back()); + assert_eq!(iter.clone().last(), raw.clone().last()); + } + + #[test] + fn assert_lower_bound_strided(data: Vec, stride: usize) { + let raw = data.iter(); + // SAFETY: The size_hint of Vec's iterator is trusted + let iter = unsafe { AssertLowerBoundOk::new(raw.clone()) }; + assert_eq!(iter.clone().nth(stride), raw.clone().nth(stride)); + assert_eq!(iter.clone().nth_back(stride), raw.clone().nth_back(stride)); + } + #[test] fn any(dataset: Vec, needle: u8) { let predicate = |&item| item == needle;