From 6351022892ab0b47e8a3a2067ec800f23be2b04a Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 18:39:04 +0100 Subject: [PATCH 01/12] docs: add doctest for `Comparable::into_inner` Signed-off-by: squidfunk --- crates/zrx-store/src/store/comparator/comparable.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/zrx-store/src/store/comparator/comparable.rs b/crates/zrx-store/src/store/comparator/comparable.rs index 7492559..414e8a0 100644 --- a/crates/zrx-store/src/store/comparator/comparable.rs +++ b/crates/zrx-store/src/store/comparator/comparable.rs @@ -81,6 +81,16 @@ impl Comparable { } /// Returns the inner value, consuming the comparable value. + /// + /// # Examples + /// + /// ``` + /// use zrx_store::comparator::{Ascending, Comparable}; + /// + /// // Create comparable value + /// let value = Comparable::new(42, Ascending); + /// assert_eq!(value.into_inner(), 42); + /// ``` #[inline] pub fn into_inner(self) -> T { self.0 From 20b187ddbe19f31fcca8206fa5398b197e6bf4fc Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 19:14:19 +0100 Subject: [PATCH 02/12] feature: add back `Stream::sort_with` and `Stream::sort_by` Signed-off-by: squidfunk --- crates/zrx-stream/src/stream/operator/sort.rs | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/crates/zrx-stream/src/stream/operator/sort.rs b/crates/zrx-stream/src/stream/operator/sort.rs index eee358d..d470062 100644 --- a/crates/zrx-stream/src/stream/operator/sort.rs +++ b/crates/zrx-stream/src/stream/operator/sort.rs @@ -25,6 +25,8 @@ //! Sort operator. +use ahash::HashMap; +use std::cmp::Ordering; use std::ops::Range; use zrx_scheduler::action::descriptor::Property; @@ -33,7 +35,7 @@ use zrx_scheduler::action::Descriptor; use zrx_scheduler::effect::Item; use zrx_scheduler::{Id, Value}; use zrx_store::decorator::Indexed; -use zrx_store::Store; +use zrx_store::{Comparator, Store}; use crate::stream::value::Position; use crate::stream::Stream; @@ -45,12 +47,12 @@ use super::{Operator, OperatorExt}; // ---------------------------------------------------------------------------- /// Sort operator. -struct Sort +struct Sort where I: Id, { /// Store of items. - store: Indexed, + store: Indexed, C>, } // ---------------------------------------------------------------------------- @@ -65,16 +67,38 @@ where pub fn sort(&self) -> Stream> { self.with_operator(Sort { store: Indexed::default() }) } + + pub fn sort_with(&self, f: F) -> Stream> + where + F: Fn(&T, &T) -> Ordering + 'static, + { + self.with_operator(Sort { + store: Indexed::with_comparator(f), + }) + } + + pub fn sort_by(&self, f: F) -> Stream> + where + F: Fn(&T) -> K + 'static, + K: Ord, + { + self.with_operator(Sort { + store: Indexed::with_comparator(move |a: &T, b: &T| { + f(a).cmp(&f(b)) + }), + }) + } } // ---------------------------------------------------------------------------- // Trait implementations // ---------------------------------------------------------------------------- -impl Operator for Sort +impl Operator for Sort where I: Id, T: Value + Clone + Ord, + C: Comparator, { type Item<'a> = Item<&'a I, Option<&'a T>>; From a35265d67b02dabd5849b40d1a278625b55397bf Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 19:44:41 +0100 Subject: [PATCH 03/12] style: harmonize trait bounds in `Debug` impls Signed-off-by: squidfunk --- crates/zrx-store/src/queue.rs | 4 ++-- crates/zrx-store/src/store/decorator/changed.rs | 4 ++-- crates/zrx-store/src/store/decorator/indexed.rs | 4 ++-- crates/zrx-store/src/store/decorator/ordered.rs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/zrx-store/src/queue.rs b/crates/zrx-store/src/queue.rs index 046a596..4ad511f 100644 --- a/crates/zrx-store/src/queue.rs +++ b/crates/zrx-store/src/queue.rs @@ -686,9 +686,9 @@ where impl fmt::Debug for Queue where - K: Key + fmt::Debug, + K: fmt::Debug + Key, V: fmt::Debug, - S: Store + fmt::Debug, + S: fmt::Debug + Store, { /// Formats the queue for debugging. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/crates/zrx-store/src/store/decorator/changed.rs b/crates/zrx-store/src/store/decorator/changed.rs index e0eb98a..91ec8ad 100644 --- a/crates/zrx-store/src/store/decorator/changed.rs +++ b/crates/zrx-store/src/store/decorator/changed.rs @@ -607,8 +607,8 @@ where impl fmt::Debug for Changed where - K: Key + fmt::Debug, - S: Store + fmt::Debug, + K: fmt::Debug + Key, + S: fmt::Debug + Store, { /// Formats the tracking decorator for debugging. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/zrx-store/src/store/decorator/indexed.rs b/crates/zrx-store/src/store/decorator/indexed.rs index 7ec8496..6eaefe4 100644 --- a/crates/zrx-store/src/store/decorator/indexed.rs +++ b/crates/zrx-store/src/store/decorator/indexed.rs @@ -880,8 +880,8 @@ where impl fmt::Debug for Indexed where - K: Key + fmt::Debug, - S: Store + fmt::Debug, + K: fmt::Debug + Key, + S: fmt::Debug + Store, C: fmt::Debug, { /// Formats the indexing decorator for debugging. diff --git a/crates/zrx-store/src/store/decorator/ordered.rs b/crates/zrx-store/src/store/decorator/ordered.rs index 6b22785..f2a9ec1 100644 --- a/crates/zrx-store/src/store/decorator/ordered.rs +++ b/crates/zrx-store/src/store/decorator/ordered.rs @@ -587,9 +587,9 @@ where impl fmt::Debug for Ordered where - K: Key + fmt::Debug, + K: fmt::Debug + Key, V: fmt::Debug, - S: Store + fmt::Debug, + S: fmt::Debug + Store, { /// Formats the ordering decorator for debugging. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { From 47f6784740e1c26ce10d03050960ec0ae134b1d9 Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 19:47:57 +0100 Subject: [PATCH 04/12] refactor: remove unnecessary clone in `Queue::set_deadline` Signed-off-by: squidfunk --- crates/zrx-store/src/queue.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zrx-store/src/queue.rs b/crates/zrx-store/src/queue.rs index 4ad511f..95c79c9 100644 --- a/crates/zrx-store/src/queue.rs +++ b/crates/zrx-store/src/queue.rs @@ -172,7 +172,7 @@ where pub fn set_deadline( &mut self, key: &K, deadline: Instant, ) -> Option { - self.store.get(key).cloned().and_then(|mut item| { + self.store.remove(key).and_then(|mut item| { item.set_deadline(deadline); self.store .insert(key.clone(), item) From 5052bc6a05bd420de9bad4fca17297faf05e6581 Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 21:04:54 +0100 Subject: [PATCH 05/12] fix: initialize `Changed::from_iter` with no changed keys Signed-off-by: squidfunk --- crates/zrx-store/src/store/decorator/changed.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/zrx-store/src/store/decorator/changed.rs b/crates/zrx-store/src/store/decorator/changed.rs index 91ec8ad..212f820 100644 --- a/crates/zrx-store/src/store/decorator/changed.rs +++ b/crates/zrx-store/src/store/decorator/changed.rs @@ -30,8 +30,8 @@ use std::vec::IntoIter; use std::{fmt, mem}; use crate::store::{ - Key, Store, StoreIntoIterator, StoreIterable, StoreKeys, StoreMut, - StoreRange, StoreValues, + Key, Store, StoreFromIterator, StoreIntoIterator, StoreIterable, StoreKeys, + StoreMut, StoreRange, StoreValues, }; // ---------------------------------------------------------------------------- @@ -497,7 +497,7 @@ where impl FromIterator<(K, V)> for Changed where K: Key, - S: StoreMut + StoreKeys + Default, + S: StoreMut + StoreFromIterator, { /// Creates a store from an iterator. /// @@ -530,11 +530,11 @@ where where T: IntoIterator, { - let mut store = Self::new(); - for (key, value) in iter { - store.insert(key, value); + Self { + store: S::from_iter(iter), + changes: HashSet::default(), + marker: PhantomData, } - store } } From 69ace7b974e3b9c3b3714a89e17f745b66d20e11 Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 21:52:19 +0100 Subject: [PATCH 06/12] style: change order of `IntoIterator` impls Signed-off-by: squidfunk --- crates/zrx-graph/src/graph/traversal/iter.rs | 40 ++++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/zrx-graph/src/graph/traversal/iter.rs b/crates/zrx-graph/src/graph/traversal/iter.rs index 09b589f..a7025e9 100644 --- a/crates/zrx-graph/src/graph/traversal/iter.rs +++ b/crates/zrx-graph/src/graph/traversal/iter.rs @@ -47,26 +47,6 @@ pub struct IntoIter { // Trait implementations // ---------------------------------------------------------------------------- -impl Iterator for IntoIter { - type Item = usize; - - /// Returns the next node. - #[inline] - fn next(&mut self) -> Option { - let node = self.traversal.take()?; - self.traversal.complete(node).expect("invariant"); - Some(node) - } - - /// Returns the bounds of the traversal. - #[inline] - fn size_hint(&self) -> (usize, Option) { - (self.traversal.len(), None) - } -} - -// ---------------------------------------------------------------------------- - impl IntoIterator for Traversal { type Item = usize; type IntoIter = IntoIter; @@ -109,3 +89,23 @@ impl IntoIterator for Traversal { IntoIter { traversal: self } } } + +// ---------------------------------------------------------------------------- + +impl Iterator for IntoIter { + type Item = usize; + + /// Returns the next node. + #[inline] + fn next(&mut self) -> Option { + let node = self.traversal.take()?; + self.traversal.complete(node).expect("invariant"); + Some(node) + } + + /// Returns the bounds on the remaining length of the traversal. + #[inline] + fn size_hint(&self) -> (usize, Option) { + (self.traversal.len(), None) + } +} From 98f5ba9a338e7711fc850bfc17b16cca870aac64 Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 21:52:57 +0100 Subject: [PATCH 07/12] style: change order of `IntoIterator` impls Signed-off-by: squidfunk --- crates/zrx-id/src/id/matcher/matches/iter.rs | 60 ++++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/crates/zrx-id/src/id/matcher/matches/iter.rs b/crates/zrx-id/src/id/matcher/matches/iter.rs index 77a5865..d92fd95 100644 --- a/crates/zrx-id/src/id/matcher/matches/iter.rs +++ b/crates/zrx-id/src/id/matcher/matches/iter.rs @@ -45,36 +45,6 @@ pub struct IntoIter { // Trait implementations // ---------------------------------------------------------------------------- -impl Iterator for IntoIter { - type Item = usize; - - /// Returns the next match. - fn next(&mut self) -> Option { - loop { - if self.block != 0 { - let num = self.block.trailing_zeros() as usize; - - // Clear the lowest bit and return it - self.block &= self.block - 1; - return Some(self.index << 6 | num); - } - - // Move to the next block - self.index += 1; - - // If all blocks are exhausted, we're done - if self.index >= self.data.len() { - return None; - } - - // Update the current block to the next block - self.block = self.data[self.index]; - } - } -} - -// ---------------------------------------------------------------------------- - impl IntoIterator for Matches { type Item = usize; type IntoIter = IntoIter; @@ -104,3 +74,33 @@ impl IntoIterator for Matches { } } } + +// ---------------------------------------------------------------------------- + +impl Iterator for IntoIter { + type Item = usize; + + /// Returns the next match. + fn next(&mut self) -> Option { + loop { + if self.block != 0 { + let num = self.block.trailing_zeros() as usize; + + // Clear the lowest bit and return it + self.block &= self.block - 1; + return Some(self.index << 6 | num); + } + + // Move to the next block + self.index += 1; + + // If all blocks are exhausted, we're done + if self.index >= self.data.len() { + return None; + } + + // Update the current block to the next block + self.block = self.data[self.index]; + } + } +} From b183bf665e3b98526432aa148510ed1cc9beaa80 Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 21:53:25 +0100 Subject: [PATCH 08/12] docs: remove non-sensical doctests from `Iterator::next` impls Signed-off-by: squidfunk --- .../zrx-graph/src/graph/visitor/ancestors.rs | 29 ------------------- .../src/graph/visitor/common_ancestors.rs | 29 ------------------- .../src/graph/visitor/common_descendants.rs | 29 ------------------- .../src/graph/visitor/descendants.rs | 29 ------------------- .../src/graph/visitor/filter_sinks.rs | 29 ------------------- .../src/graph/visitor/filter_sources.rs | 29 ------------------- crates/zrx-graph/src/graph/visitor/paths.rs | 29 ------------------- crates/zrx-graph/src/graph/visitor/sinks.rs | 29 ------------------- crates/zrx-graph/src/graph/visitor/sources.rs | 29 ------------------- 9 files changed, 261 deletions(-) diff --git a/crates/zrx-graph/src/graph/visitor/ancestors.rs b/crates/zrx-graph/src/graph/visitor/ancestors.rs index d5f60c6..9fa3f2e 100644 --- a/crates/zrx-graph/src/graph/visitor/ancestors.rs +++ b/crates/zrx-graph/src/graph/visitor/ancestors.rs @@ -111,35 +111,6 @@ impl Iterator for Ancestors<'_> { type Item = usize; /// Returns the next ancestor. - /// - /// # Examples - /// - /// ``` - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// use zrx_graph::Graph; - /// - /// // Create graph builder and add nodes - /// let mut builder = Graph::builder(); - /// let a = builder.add_node("a"); - /// let b = builder.add_node("b"); - /// let c = builder.add_node("c"); - /// - /// // Create edges between nodes - /// builder.add_edge(a, b, 0)?; - /// builder.add_edge(b, c, 0)?; - /// - /// // Create graph from builder - /// let graph = builder.build(); - /// - /// // Create iterator over ancestors - /// let mut ancestors = graph.ancestors(c); - /// while let Some(node) = ancestors.next() { - /// println!("{node:?}"); - /// } - /// # Ok(()) - /// # } - /// ``` fn next(&mut self) -> Option { // Perform a depth-first search to find all ancestors of a node, by // exploring them iteratively, not including the node itself diff --git a/crates/zrx-graph/src/graph/visitor/common_ancestors.rs b/crates/zrx-graph/src/graph/visitor/common_ancestors.rs index c4b1ad3..75ec4f2 100644 --- a/crates/zrx-graph/src/graph/visitor/common_ancestors.rs +++ b/crates/zrx-graph/src/graph/visitor/common_ancestors.rs @@ -126,35 +126,6 @@ impl Iterator for CommonAncestors<'_> { type Item = Vec; /// Returns the next layer of common ancestors. - /// - /// # Examples - /// - /// ``` - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// use zrx_graph::Graph; - /// - /// // Create graph builder and add nodes - /// let mut builder = Graph::builder(); - /// let a = builder.add_node("a"); - /// let b = builder.add_node("b"); - /// let c = builder.add_node("c"); - /// - /// // Create edges between nodes - /// builder.add_edge(a, b, 0)?; - /// builder.add_edge(a, c, 0)?; - /// - /// // Create graph from builder - /// let graph = builder.build(); - /// - /// // Create iterator over common ancestors - /// let mut ancestors = graph.common_ancestors([b, c]); - /// while let Some(nodes) = ancestors.next() { - /// println!("{nodes:?}"); - /// } - /// # Ok(()) - /// # } - /// ``` fn next(&mut self) -> Option { if self.ancestors.is_empty() { return None; diff --git a/crates/zrx-graph/src/graph/visitor/common_descendants.rs b/crates/zrx-graph/src/graph/visitor/common_descendants.rs index b9983be..138d94a 100644 --- a/crates/zrx-graph/src/graph/visitor/common_descendants.rs +++ b/crates/zrx-graph/src/graph/visitor/common_descendants.rs @@ -119,35 +119,6 @@ impl Iterator for CommonDescendants<'_> { type Item = Vec; /// Returns the next layer of common descendants. - /// - /// # Examples - /// - /// ``` - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// use zrx_graph::Graph; - /// - /// // Create graph builder and add nodes - /// let mut builder = Graph::builder(); - /// let a = builder.add_node("a"); - /// let b = builder.add_node("b"); - /// let c = builder.add_node("c"); - /// - /// // Create edges between nodes - /// builder.add_edge(a, c, 0)?; - /// builder.add_edge(b, c, 0)?; - /// - /// // Create graph from builder - /// let graph = builder.build(); - /// - /// // Create iterator over common descendants - /// let mut descendants = graph.common_descendants([a, b]); - /// while let Some(nodes) = descendants.next() { - /// println!("{nodes:?}"); - /// } - /// # Ok(()) - /// # } - /// ``` fn next(&mut self) -> Option { if self.descendants.is_empty() { return None; diff --git a/crates/zrx-graph/src/graph/visitor/descendants.rs b/crates/zrx-graph/src/graph/visitor/descendants.rs index 7b5ec44..56755d1 100644 --- a/crates/zrx-graph/src/graph/visitor/descendants.rs +++ b/crates/zrx-graph/src/graph/visitor/descendants.rs @@ -111,35 +111,6 @@ impl Iterator for Descendants<'_> { type Item = usize; /// Returns the next descendant. - /// - /// # Examples - /// - /// ``` - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// use zrx_graph::Graph; - /// - /// // Create graph builder and add nodes - /// let mut builder = Graph::builder(); - /// let a = builder.add_node("a"); - /// let b = builder.add_node("b"); - /// let c = builder.add_node("c"); - /// - /// // Create edges between nodes - /// builder.add_edge(a, b, 0)?; - /// builder.add_edge(b, c, 0)?; - /// - /// // Create graph from builder - /// let graph = builder.build(); - /// - /// // Create iterator over descendants - /// let mut descendants = graph.descendants(a); - /// while let Some(node) = descendants.next() { - /// println!("{node:?}"); - /// } - /// # Ok(()) - /// # } - /// ``` fn next(&mut self) -> Option { // Perform a depth-first search to find all descendants of a node, by // exploring them iteratively, not including the node itself diff --git a/crates/zrx-graph/src/graph/visitor/filter_sinks.rs b/crates/zrx-graph/src/graph/visitor/filter_sinks.rs index 62f08b5..3d5279a 100644 --- a/crates/zrx-graph/src/graph/visitor/filter_sinks.rs +++ b/crates/zrx-graph/src/graph/visitor/filter_sinks.rs @@ -111,35 +111,6 @@ impl Iterator for FilterSinks<'_> { type Item = usize; /// Returns the next sink. - /// - /// # Examples - /// - /// ``` - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// use zrx_graph::Graph; - /// - /// // Create graph builder and add nodes - /// let mut builder = Graph::builder(); - /// let a = builder.add_node("a"); - /// let b = builder.add_node("b"); - /// let c = builder.add_node("c"); - /// - /// // Create edges between nodes - /// builder.add_edge(a, b, 0)?; - /// builder.add_edge(b, c, 0)?; - /// - /// // Create graph from builder - /// let graph = builder.build(); - /// - /// // Create iterator over sinks - /// let mut sinks = graph.filter_sinks([a, b]); - /// while let Some(node) = sinks.next() { - /// println!("{node:?}"); - /// } - /// # Ok(()) - /// # } - /// ``` fn next(&mut self) -> Option { while self.index < self.nodes.len() { let node = self.nodes[self.index]; diff --git a/crates/zrx-graph/src/graph/visitor/filter_sources.rs b/crates/zrx-graph/src/graph/visitor/filter_sources.rs index 01b3d6b..098a6fc 100644 --- a/crates/zrx-graph/src/graph/visitor/filter_sources.rs +++ b/crates/zrx-graph/src/graph/visitor/filter_sources.rs @@ -111,35 +111,6 @@ impl Iterator for FilterSources<'_> { type Item = usize; /// Returns the next source. - /// - /// # Examples - /// - /// ``` - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// use zrx_graph::Graph; - /// - /// // Create graph builder and add nodes - /// let mut builder = Graph::builder(); - /// let a = builder.add_node("a"); - /// let b = builder.add_node("b"); - /// let c = builder.add_node("c"); - /// - /// // Create edges between nodes - /// builder.add_edge(a, b, 0)?; - /// builder.add_edge(b, c, 0)?; - /// - /// // Create graph from builder - /// let graph = builder.build(); - /// - /// // Create iterator over sources - /// let mut sources = graph.filter_sources([b, c]); - /// while let Some(node) = sources.next() { - /// println!("{node:?}"); - /// } - /// # Ok(()) - /// # } - /// ``` fn next(&mut self) -> Option { while self.index < self.nodes.len() { let node = self.nodes[self.index]; diff --git a/crates/zrx-graph/src/graph/visitor/paths.rs b/crates/zrx-graph/src/graph/visitor/paths.rs index a01dc5b..2808d0e 100644 --- a/crates/zrx-graph/src/graph/visitor/paths.rs +++ b/crates/zrx-graph/src/graph/visitor/paths.rs @@ -107,35 +107,6 @@ impl Iterator for Paths<'_> { type Item = Vec; /// Returns the next path. - /// - /// # Examples - /// - /// ``` - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// use zrx_graph::Graph; - /// - /// // Create graph builder and add nodes - /// let mut builder = Graph::builder(); - /// let a = builder.add_node("a"); - /// let b = builder.add_node("b"); - /// let c = builder.add_node("c"); - /// - /// // Create edges between nodes - /// builder.add_edge(a, b, 0)?; - /// builder.add_edge(b, c, 0)?; - /// - /// // Create graph from builder - /// let graph = builder.build(); - /// - /// // Create iterator over paths - /// let mut paths = graph.paths(a, c); - /// while let Some(path) = paths.next() { - /// println!("{path:?}"); - /// } - /// # Ok(()) - /// # } - /// ``` fn next(&mut self) -> Option { // Perform a depth-first search to find all paths from the source to // the target, and emit them in the order of discovery diff --git a/crates/zrx-graph/src/graph/visitor/sinks.rs b/crates/zrx-graph/src/graph/visitor/sinks.rs index 07bf8d4..8b86085 100644 --- a/crates/zrx-graph/src/graph/visitor/sinks.rs +++ b/crates/zrx-graph/src/graph/visitor/sinks.rs @@ -95,35 +95,6 @@ impl Iterator for Sinks<'_> { type Item = usize; /// Returns the next sink. - /// - /// # Examples - /// - /// ``` - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// use zrx_graph::Graph; - /// - /// // Create graph builder and add nodes - /// let mut builder = Graph::builder(); - /// let a = builder.add_node("a"); - /// let b = builder.add_node("b"); - /// let c = builder.add_node("c"); - /// - /// // Create edges between nodes - /// builder.add_edge(a, b, 0)?; - /// builder.add_edge(b, c, 0)?; - /// - /// // Create graph from builder - /// let graph = builder.build(); - /// - /// // Create iterator over sinks - /// let mut sinks = graph.sinks(); - /// while let Some(node) = sinks.next() { - /// println!("{node:?}"); - /// } - /// # Ok(()) - /// # } - /// ``` fn next(&mut self) -> Option { while self.index < self.outgoing.len() { let node = self.index; diff --git a/crates/zrx-graph/src/graph/visitor/sources.rs b/crates/zrx-graph/src/graph/visitor/sources.rs index 7d6a25b..d6655c5 100644 --- a/crates/zrx-graph/src/graph/visitor/sources.rs +++ b/crates/zrx-graph/src/graph/visitor/sources.rs @@ -95,35 +95,6 @@ impl Iterator for Sources<'_> { type Item = usize; /// Returns the next source. - /// - /// # Examples - /// - /// ``` - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// use zrx_graph::Graph; - /// - /// // Create graph builder and add nodes - /// let mut builder = Graph::builder(); - /// let a = builder.add_node("a"); - /// let b = builder.add_node("b"); - /// let c = builder.add_node("c"); - /// - /// // Create edges between nodes - /// builder.add_edge(a, b, 0)?; - /// builder.add_edge(b, c, 0)?; - /// - /// // Create graph from builder - /// let graph = builder.build(); - /// - /// // Create iterator over sources - /// let mut sources = graph.sources(); - /// while let Some(node) = sources.next() { - /// println!("{node:?}"); - /// } - /// # Ok(()) - /// # } - /// ``` fn next(&mut self) -> Option { while self.index < self.incoming.len() { let node = self.index; From 3b9b02f616288f4f28915777fd286f60f1c13c97 Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 21:54:04 +0100 Subject: [PATCH 09/12] performance: improve `IntoIterator` impl for `Changed` decorator Signed-off-by: squidfunk --- crates/zrx-store/src/store/decorator/changed.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/zrx-store/src/store/decorator/changed.rs b/crates/zrx-store/src/store/decorator/changed.rs index 212f820..057c861 100644 --- a/crates/zrx-store/src/store/decorator/changed.rs +++ b/crates/zrx-store/src/store/decorator/changed.rs @@ -26,7 +26,6 @@ use ahash::{HashMap, HashSet}; use std::borrow::Borrow; use std::marker::PhantomData; use std::ops::RangeBounds; -use std::vec::IntoIter; use std::{fmt, mem}; use crate::store::{ @@ -544,7 +543,7 @@ where S: Store + StoreIntoIterator, { type Item = (K, V); - type IntoIter = IntoIter; + type IntoIter = S::IntoIter; /// Creates an iterator over the store. /// @@ -569,7 +568,7 @@ where /// } /// ``` fn into_iter(self) -> Self::IntoIter { - self.store.into_iter().collect::>().into_iter() + self.store.into_iter() } } From 04a5761c3b891f2be14fa46394d64b3d5b67f30f Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 21:55:08 +0100 Subject: [PATCH 10/12] performance: improve `IntoIterator` impl for `Indexed` decorator Signed-off-by: squidfunk --- .../zrx-store/src/store/decorator/indexed.rs | 49 +------ .../src/store/decorator/indexed/iter.rs | 129 ++++++++++++++++++ 2 files changed, 133 insertions(+), 45 deletions(-) create mode 100644 crates/zrx-store/src/store/decorator/indexed/iter.rs diff --git a/crates/zrx-store/src/store/decorator/indexed.rs b/crates/zrx-store/src/store/decorator/indexed.rs index 6eaefe4..b4b4fa9 100644 --- a/crates/zrx-store/src/store/decorator/indexed.rs +++ b/crates/zrx-store/src/store/decorator/indexed.rs @@ -31,13 +31,16 @@ use std::cmp::Ordering; use std::fmt; use std::marker::PhantomData; use std::ops::{Bound, Index, Range, RangeBounds}; -use std::vec::IntoIter; use crate::store::comparator::{Ascending, Comparator}; use crate::store::{ Key, Store, StoreIterable, StoreKeys, StoreMut, StoreValues, }; +mod iter; + +pub use iter::IntoIter; + // ---------------------------------------------------------------------------- // Structs // ---------------------------------------------------------------------------- @@ -801,50 +804,6 @@ where } } -impl IntoIterator for Indexed -where - K: Key, - S: StoreMut, -{ - type Item = (K, V); - type IntoIter = IntoIter; - - /// Creates an iterator over the store. - /// - /// This method consumes the store, and collects it into a vector, since - /// there's currently no way to implement this due to the absence of ATPIT - /// (associated type position impl trait) support in stable Rust. When the - /// feature is stabilized, we can switch to a more efficient approach. - /// - /// # Examples - /// - /// ``` - /// use zrx_store::decorator::Indexed; - /// use zrx_store::StoreMut; - /// - /// // Create store and initial state - /// let mut store = Indexed::default(); - /// store.insert("key", 42); - /// - /// // Create iterator over the store - /// for (key, value) in store { - /// println!("{key}: {value}"); - /// } - /// ``` - fn into_iter(mut self) -> Self::IntoIter { - self.ordering - .into_iter() - .map(|key| { - self.store - .remove(&key) - .map(|value| (key, value)) - .expect("invariant") - }) - .collect::>() - .into_iter() - } -} - // ---------------------------------------------------------------------------- #[allow(clippy::implicit_hasher)] diff --git a/crates/zrx-store/src/store/decorator/indexed/iter.rs b/crates/zrx-store/src/store/decorator/indexed/iter.rs new file mode 100644 index 0000000..7c546c1 --- /dev/null +++ b/crates/zrx-store/src/store/decorator/indexed/iter.rs @@ -0,0 +1,129 @@ +// Copyright (c) 2025-2026 Zensical and contributors + +// SPDX-License-Identifier: MIT +// All contributions are certified under the DCO + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// ---------------------------------------------------------------------------- + +//! Iterator over an indexing decorator. + +use std::marker::PhantomData; +use std::vec; + +use crate::store::{Key, StoreMut}; + +use super::Indexed; + +// ---------------------------------------------------------------------------- +// Structs +// ---------------------------------------------------------------------------- + +/// Iterator over an indexing decorator. +#[derive(Debug)] +pub struct IntoIter { + /// Underlying store. + store: S, + /// Ordering of values. + ordering: vec::IntoIter, + /// Marker for types. + marker: PhantomData, +} + +// ---------------------------------------------------------------------------- +// Trait implementations +// ---------------------------------------------------------------------------- + +impl IntoIterator for Indexed +where + K: Key, + S: StoreMut, +{ + type Item = (K, V); + type IntoIter = IntoIter; + + /// Creates an iterator over the store. + /// + /// This method consumes the store, and collects it into a vector, since + /// there's currently no way to implement this due to the absence of ATPIT + /// (associated type position impl trait) support in stable Rust. When the + /// feature is stabilized, we can switch to a more efficient approach. + /// + /// # Examples + /// + /// ``` + /// use zrx_store::decorator::Indexed; + /// use zrx_store::StoreMut; + /// + /// // Create store and initial state + /// let mut store = Indexed::default(); + /// store.insert("key", 42); + /// + /// // Create iterator over the store + /// for (key, value) in store { + /// println!("{key}: {value}"); + /// } + /// ``` + #[inline] + fn into_iter(self) -> Self::IntoIter { + IntoIter { + store: self.store, + ordering: self.ordering.into_iter(), + marker: PhantomData, + } + } +} + +// ---------------------------------------------------------------------------- + +impl Iterator for IntoIter +where + K: Key, + S: StoreMut, +{ + type Item = (K, V); + + /// Returns the next item. + #[inline] + fn next(&mut self) -> Option { + match self.ordering.next() { + Some(key) => self.store.remove(&key).map(|value| (key, value)), + None => None, + } + } + + /// Returns the bounds on the remaining length of the iterator. + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.ordering.size_hint() + } +} + +impl ExactSizeIterator for IntoIter +where + K: Key, + S: StoreMut, +{ + /// Returns the exact remaining length of the iterator. + #[inline] + fn len(&self) -> usize { + self.ordering.len() + } +} From fbe7f9daae2e8198a43d01c8f9252342edc10237 Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 21:55:36 +0100 Subject: [PATCH 11/12] performance: improve `IntoIterator` impl for `Ordered` decorator Signed-off-by: squidfunk --- .../zrx-store/src/store/decorator/ordered.rs | 47 +----- .../src/store/decorator/ordered/iter.rs | 139 ++++++++++++++++++ 2 files changed, 143 insertions(+), 43 deletions(-) create mode 100644 crates/zrx-store/src/store/decorator/ordered/iter.rs diff --git a/crates/zrx-store/src/store/decorator/ordered.rs b/crates/zrx-store/src/store/decorator/ordered.rs index f2a9ec1..9fe9c7d 100644 --- a/crates/zrx-store/src/store/decorator/ordered.rs +++ b/crates/zrx-store/src/store/decorator/ordered.rs @@ -29,13 +29,16 @@ use ahash::HashMap; use std::borrow::Borrow; use std::collections::BTreeMap; use std::fmt; -use std::vec::IntoIter; use crate::store::comparator::{Ascending, Comparable, Comparator}; use crate::store::{ Key, Store, StoreIterable, StoreKeys, StoreMut, StoreValues, }; +mod iter; + +pub use iter::IntoIter; + // ---------------------------------------------------------------------------- // Structs // ---------------------------------------------------------------------------- @@ -510,48 +513,6 @@ where } } -impl IntoIterator for Ordered -where - K: Key, - V: Clone, - S: Store, -{ - type Item = (K, V); - type IntoIter = IntoIter; - - /// Creates an iterator over the store. - /// - /// This method consumes the store, and collects it into a vector, since - /// there's currently no way to implement this due to the absence of ATPIT - /// (associated type position impl trait) support in stable Rust. When the - /// feature is stabilized, we can switch to a more efficient approach. - /// - /// # Examples - /// - /// ``` - /// use zrx_store::decorator::Ordered; - /// use zrx_store::StoreMut; - /// - /// // Create store and initial state - /// let mut store = Ordered::default(); - /// store.insert("key", 42); - /// - /// // Create iterator over the store - /// for (key, value) in store { - /// println!("{key}: {value}"); - /// } - /// ``` - fn into_iter(self) -> Self::IntoIter { - self.ordering - .into_iter() - .flat_map(|(value, keys)| { - keys.into_iter().map(move |key| (key, value.clone())) - }) - .collect::>() - .into_iter() - } -} - // ---------------------------------------------------------------------------- #[allow(clippy::implicit_hasher)] diff --git a/crates/zrx-store/src/store/decorator/ordered/iter.rs b/crates/zrx-store/src/store/decorator/ordered/iter.rs new file mode 100644 index 0000000..b7e90ac --- /dev/null +++ b/crates/zrx-store/src/store/decorator/ordered/iter.rs @@ -0,0 +1,139 @@ +// Copyright (c) 2025-2026 Zensical and contributors + +// SPDX-License-Identifier: MIT +// All contributions are certified under the DCO + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// ---------------------------------------------------------------------------- + +//! Iterator over an ordering decorator. + +use std::collections::btree_map; +use std::vec; + +use crate::store::comparator::Comparable; +use crate::store::{Key, Store}; + +use super::Ordered; + +// ---------------------------------------------------------------------------- +// Structs +// ---------------------------------------------------------------------------- + +/// Iterator over an ordering decorator. +#[derive(Debug)] +pub struct IntoIter { + /// Ordering of values. + ordering: btree_map::IntoIter, Vec>, + /// Current value. + value: Option, + /// Current keys. + keys: vec::IntoIter, +} + +// ---------------------------------------------------------------------------- +// Trait implementations +// ---------------------------------------------------------------------------- + +impl IntoIterator for Ordered +where + K: Key, + V: Clone, + S: Store, +{ + type Item = (K, V); + type IntoIter = IntoIter; + + /// Creates an iterator over the store. + /// + /// # Examples + /// + /// ``` + /// use zrx_store::decorator::Ordered; + /// use zrx_store::StoreMut; + /// + /// // Create store and initial state + /// let mut store = Ordered::default(); + /// store.insert("key", 42); + /// + /// // Create iterator over the store + /// for (key, value) in store { + /// println!("{key}: {value}"); + /// } + /// ``` + #[inline] + fn into_iter(self) -> Self::IntoIter { + IntoIter { + ordering: self.ordering.into_iter(), + value: None, + keys: vec::IntoIter::default(), + } + } +} + +// ---------------------------------------------------------------------------- + +impl Iterator for IntoIter +where + K: Key, + V: Clone, +{ + type Item = (K, V); + + /// Returns the next item. + #[inline] + fn next(&mut self) -> Option { + loop { + // Check if we have keys left with the current value + if let Some(key) = self.keys.next() { + return self.value.clone().map(|v| (key, v)); + } + + // Fetch the next value and associated keys + if let Some((value, keys)) = self.ordering.next() { + self.value = Some(value.into_inner()); + self.keys = keys.into_iter(); + } else { + break; + } + } + + // No more items to return + None + } + + /// Returns the bounds on the remaining length of the iterator. + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.ordering.size_hint() + } +} + +impl ExactSizeIterator for IntoIter +where + K: Key, + V: Clone, +{ + /// Returns the exact remaining length of the iterator. + #[inline] + fn len(&self) -> usize { + self.ordering.len() + } +} From 03591de3b9856d5f90436072e5780e5fec467e8b Mon Sep 17 00:00:00 2001 From: squidfunk Date: Sat, 17 Jan 2026 21:55:48 +0100 Subject: [PATCH 12/12] feature: export `IntoIter` impls of decorators Signed-off-by: squidfunk --- crates/zrx-store/src/store/decorator.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/zrx-store/src/store/decorator.rs b/crates/zrx-store/src/store/decorator.rs index a0dfc52..10903c7 100644 --- a/crates/zrx-store/src/store/decorator.rs +++ b/crates/zrx-store/src/store/decorator.rs @@ -25,9 +25,9 @@ //! Store decorators. -mod changed; -mod indexed; -mod ordered; +pub mod changed; +pub mod indexed; +pub mod ordered; pub use changed::Changed; pub use indexed::Indexed;