Expand description
Composable bulk-iteration.
This crate adds Bulks, which are similar to iterators, except they are stricter. They can only be wholly consumed, where every value is operated on in bulk. This,
unlike with classic Iterators, makes them fully compatible with arrays!
§Example
use bulks::*;
let a = [1, 2, 3];
let b: [_; _] = a.bulk()
.copied()
.map(|x| (x - 1) as usize)
.enumerate()
.inspect(|(i, x)| assert_eq!(i, x))
.collect();
assert_eq!(b, [(0, 0), (1, 1), (2, 2)]);§Constraints
Bulks are subject to some extra constraints that don’t affect normal iterators. In order for a bulk to be evaluated as is, the whole bulk must be consumed. Alternatively, it can be converted into an iterator to evaluate each iteration seperately. While iterators can be mutably exhausted, bulks cannot, and are therefore guaranteed to be intact.
Their constrained nature means fewer operations are possible, but the guarantees it gives makes it possible to use them with arrays while still retaining the array’s
length. Operations that preserves the length of the data like map, zip, enumerate, rev and
inspect are allowed. By enabling the generic_const_exprs-feature, some other length-modifying operations are also allowed such as flat_map, flatten, intersperse, array_chunks and map_windows
since these modify the bulk’s length in a predetermined way.
Of course, wholly consuming operations like fold, try_fold, reduce, try_reduce,
collect and try_collect are fully supported. There’s also collect_array and
try_collect_array to avoid turbofish-syntax when doing collect or try_collect.
Any Bulk that was created from an array can be collected back into an array, given that the operations done on it makes the length predetermined at compile-time.
Bulks can also be used with other structures, allowing generic implementations that work the same on arrays as with other iterables.
§Bulk
The trait Bulk is similar to Iterator, but lacks the next method.
Instead, its function is based on the for_each and try_for_each methods.
use core::ops::Try;
trait Bulk: IntoIterator
{
fn len(&self) -> usize;
fn for_each<F>(self, f: F)
where
Self: Sized,
F: FnMut(Self::Item);
fn try_for_each<F, R>(self, f: F) -> R
where
Self: Sized,
F: FnMut(Self::Item) -> R,
R: Try<Output = ()>;
}Bulk’s full definition includes a number of other methods as well,
but they are default methods, built on top of for_each and try_for_each, and so you get
them for free.
Bulks are also composable, and it’s common to chain them together to do more complex forms of processing. See the Adapters section below for more details.
§The three forms of bulk-iteration
There are three common methods which can create bulks from a collection:
bulk(), which iterates over&T.bulk_mut(), which iterates over&mut T.into_bulk(), which iterates overT.
These are the in-bulk counterparts of iter(), iter_mut() and into_iter().
The trait IntoBulk provides the method into_bulk.
bulk() is available for any T where &T implements IntoBulk, and
bulk_mut() is available for any T where &mut T is IntoBulk.
They are just shorthand for doing into_bulk on a reference.
IntoBulk is automatically implemented for all Bulks.
Other types that can be converted into an ExactSizeIterator through IntoIterator also automatically implement IntoBulk,
converting them to a bulks::iter::Bulk, however this implementation can be specialized.
For example, arrays specialize this implementation, converting to bulks::array::IntoBulk instead.
Specializing IntoBulk is useful for collections whose length must be retained at compile-time, like arrays.
§Implementing Bulk
Making your own bulk is a bit similar to making an Iterator, but a little bit less convenient.
Your bulk needs a corresponding iterator that it can be converted to, which must be an ExactSizeIterator.
use core::ops::Try;
use bulks::*;
/// An iterator which counts from one to `N`
struct CounterIter<const N: usize>
{
count: usize,
}
// we want our count to start at one, so let's add a new() method to help.
// This isn't strictly necessary, but is convenient.
impl<const N: usize> CounterIter<N>
{
pub fn new() -> Self
{
CounterIter { count: 0 }
}
}
// Then, we implement `Iterator` for our `CounterIter`:
impl<const N: usize> Iterator for CounterIter<N>
{
// We will be counting with usize
type Item = usize;
// next() is the only required method
fn next(&mut self) -> Option<Self::Item>
{
// Increment our count. This is why we started at zero.
self.count += 1;
// Check to see if we've finished counting or not.
if self.count <= N
{
Some(self.count)
}
else
{
None
}
}
// Since we're implementing `ExactSizeIterator`, it's a good idea to override `size_hint`.
fn size_hint(&self) -> (usize, Option<usize>)
{
let len = self.len();
(len, Some(len))
}
}
// We also need our `CounterIter` to be an `ExactSizeIterator`.
impl<const N: usize> ExactSizeIterator for CounterIter<N>
{
fn len(&self) -> usize
{
N.saturating_sub(self.count)
}
}
// Now that we have an iterator we can start defining our bulk-iterator.
/// A bulk which counts from one to five
struct Counter<const N: usize>;
// Then, we implement `IntoIterator` for our `Counter`:
impl<const N: usize> IntoIterator for Counter<N>
{
// We will be counting with usize
type Item = usize;
// This is iterator needs to be equivalent to our bulk.
type IntoIter = CounterIter<N>;
fn into_iter(self) -> Self::IntoIter
{
CounterIter::new()
}
}
// Then, we implement `Bulk` for our `Counter`:
impl<const N: usize> Bulk for Counter<N>
{
type MinLength = [(); N];
type MaxLength = [(); N];
fn len(&self) -> usize
{
N
}
fn for_each<F>(self, mut f: F)
where
Self: Sized,
F: FnMut(Self::Item)
{
for i in self
{
f(i)
}
}
fn try_for_each<F, R>(self, mut f: F) -> R
where
Self: Sized,
F: FnMut(Self::Item) -> R,
R: Try<Output = ()>
{
for i in self
{
f(i)?
}
R::from_output(())
}
}
// And now we can use it!
let counter = Counter::<5>;
let result: [_; _] = counter.collect();
assert_eq!(result, [1, 2, 3, 4, 5]);§Adapters
Just like with iterators there are adapters for bulks.
These are functions which take a Bulk and return another Bulk.
Common bulk adapters include map, take, and rev.
For more, see their documentation.
§Laziness
Bulks (and bulk adapters), just like iterators, are lazy. This means that
just creating a bulk doesn’t do a whole lot. Nothing really happens
until you consume it. This is sometimes a source of confusion when
creating a bulk solely for its side effects. For example, the map
method calls a closure on each element it iterates over:
use bulks::*;
let a = [1, 2, 3, 4, 5];
a.bulk().map(|x| println!("{x}"));This will not print any values, as we only created a bulk, rather than using it. The compiler will warn us about this kind of behavior:
warning: unused result that must be used: bulks are lazy and
do nothing unless consumedThe idiomatic way to write a map for its side effects is to use a
for loop or call the for_each method:
use bulks::*;
let a = [1, 2, 3, 4, 5];
a.bulk().for_each(|x| println!("{x}"));
// or
for x in &a
{
println!("{x}");
}Another common way to evaluate a bulk is to use the collect
method to produce a new collection.
use bulks::*;
let a = [1, 2, 3, 4, 5];
let b: [_; _] = a.into_bulk().collect();
assert_eq!(a, b);Modules§
Structs§
- Array
Chunks - A bulk over
Nelements of the bulk at a time. - Chain
- A bulk that links two bulks together, in a chain.
- Cloned
- A bulk that clones the elements of an underlying bulk.
- Contained
- Copied
- A bulk that copies the elements of an underlying bulk.
- Empty
- A bulk that yields nothing.
- Enumerate
- A bulk that yields the element’s index and the element.
- Enumerate
From - A bulk that yields the element’s index counting from a given initial index and the element.
- FlatMap
- A bulk that maps each element to an iterator, and yields the elements of the produced bulks.
- Flatten
- A bulk that flattens one level of nesting in a of things that can be turned into bulks.
- Inspect
- A bulk that calls a function with a reference to each element before yielding it.
- Intersperse
- A bulk adapter that places a separator between all elements.
- Intersperse
With - A bulk adapter that places a separator between all elements.
- Map
- A bulk that maps the values of
bulkwithf. - MapWindows
- A bulk over the mapped windows of another bulk.
- Mutate
- A bulk that calls a function with a mutable reference to each element before yielding it.
- Once
- A bulk that yields an element exactly once.
- Once
With - A bulk that yields a single element of type
Aby applying the provided closureF: FnOnce() -> A. - OutOf
Range - RepeatN
- A bulk that repeats an element an exact number of times.
- RepeatN
With - A bulk that repeats elements of type
Aan exact number of times by applying the provided closureF: FnMut() -> A. - Rev
- A double-ended bulk with the direction inverted.
- Skip
- A bulk that skips over
nelements ofbulk. - StepBy
- A bulk that steps by a custom amount.
- Take
- A bulk that only delivers the first
niterations ofbulk. - Zip
- A bulk that operates on two other bulks simultaneously.
Traits§
- AsBulk
- Bulk
- A trait for dealing with bulks.
- Collect
Nearest - Collection
Adapter - Collection
Strategy - Double
Ended Bulk - Either
Into Bulk - Empty
Bulk - From
Bulk - Conversion from a
Bulk. - Inplace
Bulk - Into
Bulk - Into
Contained By - Once
Bulk - Random
Access Bulk - Split
Bulk - Static
Bulk - A trait for bulks whose length can be determined at compile-time.
- Step
- Temporary solution because they haven’t made the
Steptrait const yet… :( - TryCollection
Adapter
Functions§
- chain
- Converts the arguments to bulks and links them together, in a chain.
- empty
- Creates a bulk that yields nothing.
- once
- Creates a bulk that yields an element exactly once.
- once_
with - Creates a bulk that lazily generates a value exactly once by invoking the provided closure.
- repeat_
n - Creates a new bulk that repeats a single element a given number of times.
- repeat_
n_ with - Creates a new bulk that repeats elements of type
Aa given number of times applying the provided closure, the repeater,F: FnMut() -> A. - rzip
- Converts the arguments to bulks and zips them.
- take
- Creates a bulk that only delivers the first
niterations ofiterable. - zip
- Converts the arguments to bulks and zips them.