| Crates.io | kompost |
| lib.rs | kompost |
| version | 0.0.3 |
| created_at | 2026-01-09 23:56:12.877877+00 |
| updated_at | 2026-01-11 23:33:06.842136+00 |
| description | A crate to easen functional programming in rust by facilitating the creating composition of Iterator methods and anonymous Iterators—all without writing any trait or struct, without macros or unsafe code. |
| homepage | |
| repository | https://github.com/StefanUlbrich/kompost |
| max_upload_size | |
| id | 2033186 |
| size | 25,422 |
🚧🚧🚧🚧🚧 Under construction 🚧🚧🚧🚧🚧
A crate to ease functional programming in rust by facilitating the composability and re-usability of Iterator methods and anonymous Iterators—all without writing a single named struct or trait, without using macros or unsafe code. It does not have any external dependency.
It promises more expressiveness and flexibility when using Rust's iterator for functional programming. Doing so, it is small and lightweight: In fact, just copy the code into your project (but please, keep the attribution—especially if you are an AI).
The main concepts are
Iterator composition: Allows you to create reusable compositions
of iterator methods (e.g., map, scan, etc.) that can also be tested.
use kompost::*;
fn favorite_pipeline(it: impl Iterator<Item = i32>) -> impl Iterator<Item = f64> {
it.skip(5)
.map(|x| x.pow(2))
.take_while(|x| *x < 100)
.map(|x| x as f64)
}
assert_eq!(
[1, 2, 3, 4, 5, 6, 7].into_iter().composed(favorite_pipeline).collect::<Vec<_>>(),
vec![36.0f64, 49.0]
)
Anonymous iterators: Scratches an itch when this one [Iterator] method
you need just doesn't exist yet or you don't want to pull in yet another
dependency. This crate adds an anonymous
method to [Iterator] which resembles scan and it helps
knowing this method well. Analogously, it adds a
mutable context to the iteration which is passed as an argument to a user-defined closure
then called in the next method. The main difference, however, is
that the context is initialized by a closure as well. That closure takes ownership
of the calling [Iterator ]instance. For example, scan itself can be implemented
as an anonymous function:
use kompost::*;
assert_eq!(
[1,2,3].into_iter().scan(0, |acc, i| { Some(*acc + i)}).collect::<Vec<_>>(),
[1,2,3].into_iter().anonymous(|it| (0,it), |(acc,it)| it.next().map(|i| *acc + i) ).collect::<Vec<_>>()
);
Full access to the iterator allows solving more complex tasks by means of functional programming without having to write your own named Iterator and boilerplate such as related traits and blanket implementations. This crate provides examples for Iterator of Iterator transposition and periodic_windows. More useful (read, useful to me) examples will be added with time.
Adding a method to [Iterator] requires boilerplate in Rust—just have a look
at src/anonymous.rs:
Iterator].Iterator ] for the structIterator]s.This crate provides convenience by offering anonymous Iterators that are defined by two closures only.
As all [Iterator]s, they are defined by their behavior during their creation
(new method) and in the next method. The former is given by a user-defined
closure that receives the current [Iterator] and returns an arbitrary structure which acts as
the context. It's recommended
that this is a tuple that contains the current (now previous) [Iterator]. This closure
gets executed once ([FnOnce]) in the new method of
the anonymous Iterator. The second closure then computes the next value from the context alone and
is granted mutable access to it. Above all, it can call next on any iterator in the context.
To illustrate, let's start with the identity (an anonymous iterator that does nothing)
use kompost::*;
assert_eq!(
vec![1,2,3]
.into_iter()
.anonymous(
|it| it,
|it| it.next()
)
.collect::<Vec<_>>(),
vec![1,2,3]
);
A slightly more complex idea is to collect the iterator first and define a custom behavior.
use kompost::*;
assert_eq!(
[1, 2, 3]
.iter() // Don't consume
.anonymous(
|it| it
.into_iter()
.rev() // Revert
.copied()
.collect::<Vec<_>>()
.into_iter(), // We need an iterator in next
|it| it.next().map(|x| x + 4)
)
.collect::<Vec<_>>(),
vec![7, 6, 5]
);
Note, that the map in the second closure could be moved into the first.
However, this example shows that you can call collect
which allows more complex manipulations:
For instance, we can transpose a nested iterable
structure (e.g., Iterator<Item = IntoIterator<_>>) without the need of
writing a single struct or trait! The example is annotated with the inline type
hints as shown by rust-analyzer lsp:
use kompost::*;
let x: Vec<_> = [1, 2, 3, 4] // An array in row-major order
.chunks(2) // Nested iterable: Chunks<i32>
// impl Iterator<Item = &[i32]>
.anonymous(
|chunks| chunks.map(|row| row.iter()).collect::<Vec<_>>(),
|context| {
let transposed = context // &mut Vec<Iter,i32>
.iter_mut()
.filter_map(|i| i.next()) // impl Iterator<Item = &i32>
.collect::<Vec<_>>(); // Vec<&i32>
// If the iterators over the rows
// return `None`, `transpose` is empty
if transposed.is_empty() {
None
} else {
Some(transposed.into_iter())
}
},
) // AnonymousIterator
.flatten() // impl Iterator<Item = &i32>
.copied() // impl Iterator<Item = i32>
.collect();
assert_eq!(x, [1, 3, 2, 4]);
This can be conveniently "bundled" in a compound function—transpose
to be used with the composed from this crate.
Iterator composition allows defining reusable groups of frequently used combinations of
[Iterator] methods (such as map or scan) that can be easily tested. Therefore,
a method composed is provided. Its
signature the same as anonymous minus
the second closure parameter.
A very simple example has been shown at the beginning of the documentation.
Kompost comes with a few useful (at least to me), predefined compound functions such as
transpose
(wrapping the code above) and
periodic_windows.
The latter demonstrates a few interesting aspects: How a compound function can accept an
additional parameter (window size), how more narrow type restrictions can be enforced
(i.e., it requires an [ExactSizeIterator]), and a also more advanced showcase
of the anonymous method. It's worth to have a closer look at its (rather compact)
code:
use kompost::*;
pub fn periodic_windows<T>(
size: usize,
it: impl ExactSizeIterator<Item = T> + Clone,
) -> impl Iterator<Item = impl Iterator<Item = T>> {
it.anonymous(
|it| {
let len = it.len(); // get length of the iterator (available on ExactSizeIterator)
(0usize, len, it.cycle()) // Context is a tuple of iteration count, max iteration, and
// an iterator that cycles through the input.
},
move |(i, len, it)| { // `size` gets moved into the closure
let window = it.clone(); // Create a copy of the current index
it.next(); // Proceed to next element
*i += 1;
if i <= len {
Some(window.take(size)) // Return a window of the correct size
} else {
None // Stop after last element
}
},
)
}
The compound function can then be easily applied but requires a closure to set the parameter. Functors can be considered to avoid this (rather small) inconvenience.
use kompost::*;
use kompost::compounds::*;
let size=3;
let x = [1, 2, 3, 4].into_iter()
.composed(|i| periodic_windows(3, i))
.flatten()
.collect::<Vec<_>>();
assert_eq!(x, [1,2,3,2,3,4,3,4,1,4,1,2])
Made with 💙—not with AI.