This crate provides a [`ShadowCountedIter`] which counts every iteration to a hidden
counter. It is possible to create nested iterators which can commit their counter to their parent
iterator. Additionally, iterators can carry supplemental data through the iteration process.
Unlike the std [`std::iter::Enumerate`] iterator, the [`ShadowCountedIter`] does not return the counter
to the user, instead it has to be queried with the [`ShadowCountedIter::counter()`] method.
We also provide a [`IntoShadowCounted`] extension trait which converts any iterator into a
[`ShadowCountedIter`].
## Features
- **Basic counting**: Track iteration progress without returning indices
- **Nested iteration**: Create child iterators that inherit parent state
- **Commit mechanism**: Propagate counts from nested iterators to parents
- **Supplemental data**: Carry arbitrary data through iteration with optional commit logic
## Examples
### Basic Counting
```rust
use shadow_counted::{ShadowCountedIter, IntoShadowCounted};
let vec = vec![1, 2, 3];
let mut iter = vec.into_iter().shadow_counted();
while let Some(_) = iter.next() {}
assert_eq!(iter.counter(), 3);
```
### Nested Counting
```rust
use shadow_counted::{ShadowCountedIter, IntoShadowCounted};
// Make a datastructure that may hold nested elements.
#[derive(Debug, PartialEq)]
enum Nodes<'a, T> {
Leaf(T),
Nested(&'a [Nodes<'a, T>]),
}
let items = &[
Nodes::Leaf(1),
Nodes::Nested(&[Nodes::Leaf(2), Nodes::Leaf(3)]),
Nodes::Leaf(4),
];
// iterate over the outer
let mut sc_iter = items.into_iter().shadow_counted();
assert_eq!(sc_iter.next(), Some(&Nodes::Leaf(1)));
// the 2nd element is `Node::Nested(..)'
let element = sc_iter.next().unwrap();
# assert_eq!(element, &Nodes::Nested(&[Nodes::Leaf(2), Nodes::Leaf(3)]));
// since we dont want to count `Nested` we substract one from the counter
sc_iter.add(-1);
let Nodes::Nested(nested) = element else {unreachable!()};
let mut nested_iter = nested.into_iter().nested_shadow_counted(&mut sc_iter);
# assert_eq!(nested_iter.counter(), 1);
assert_eq!(nested_iter.next(), Some(&Nodes::Leaf(2)));
# assert_eq!(nested_iter.counter(), 2);
assert_eq!(nested_iter.next(), Some(&Nodes::Leaf(3)));
# assert_eq!(nested_iter.counter(), 3);
// reaching the end, commit to the parent iter
assert_eq!(nested_iter.next(), None);
// eventually a nested iter must be committed when its progress should be counted
nested_iter.commit();
// back to the outer
assert_eq!(sc_iter.counter(), 3);
assert_eq!(sc_iter.next(), Some(&Nodes::Leaf(4)));
# assert_eq!(sc_iter.counter(), 4);
# assert_eq!(sc_iter.next(), None);
assert_eq!(sc_iter.counter(), 4);
```
### Supplemental Data
You can attach supplemental data to iterators that gets cloned when nesting and can be
committed back to the parent. The parent data must use interior mutability (like `Cell`)
to allow updates during commit:
```rust
use shadow_counted::{Commit, ShadowCountedIter, IntoShadowCounted};
use std::cell::Cell;
#[derive(Clone, Default, Debug)]
struct Stats {
count: Cell<u32>,
}
impl Commit for Stats {
fn commit(&self, from: Self) {
self.count.set(self.count.get() + from.count.get());
}
}
let mut parent = ShadowCountedIter::new_with(
vec![1, 2].into_iter(),
Stats::default()
);
let mut nested = vec![3, 4].into_iter().nested_shadow_counted(&mut parent);
nested.supplement_mut().count.set(5);
nested.commit().unwrap();
// Stats were committed from nested to parent
assert_eq!(parent.supplement().count.get(), 5);
```
# Development
shadow_counted is distributed via [radicle](https://radicle.xyz/) its identifier is
[rad:zuUvsqfRdsC1NraTegYHBMtCmm2Z](https://seed.pipapo.org/nodes/seed.pipapo.org/rad:zuUvsqfRdsC1NraTegYHBMtCmm2Z)
You can clone the repository using git by:
`git clone https://seed.pipapo.org/zuUvsqfRdsC1NraTegYHBMtCmm2Z.git shadow_counted`
When you spot a problem or need a new feature feel free to open an
[issue](https://radicle.xyz/guides/user#working-with-issues) or (prefered!) send a
[PR](https://radicle.xyz/guides/user#working-with-patches).
Commits and other git operations are augmented and validated with
[bar](https://seed.pipapo.org/nodes/seed.pipapo.org/rad:z3WhBdHt1VVNyeQ61zpY66pNzuSrP). For
contributors it is recommened to enable bar too by calling `./bar activate`
within a checked out unsynn repository.
## Contribution/Coding Guidelines
Chances to get contributions merged increase when you:
* Include documentation following the existing documentation practice. Write examples/doctests.
* Passing `./bar lints` is a absolute requirement, I am not even looking at contributions
that fail basic lint and formatting checks. Hint: try `./bar dwim` to apply formatting and
trivial fixes.
* Ideally passing `./bar` without errors or warnings. Although if some problems and errors
remain to be discussed then a WIP-PR failing tests is temporary acceptable.
* Passing test-coverage with `./bar cargo_mutants`.
* When you activated the githooks with './bar activate' then the policies for viable commits
are enforced automatically.
* Implement reasonable complete things. Not everything needs to be included in a first
version, but it must be usable.