Expand description
Adds vector, set, map, and iterator comprehensions to Rust. This
is achieved through a functional macro, iter_comp!
, that
expands to iterators.
Usage
Basic Usage
The core idea is simple: provide an easy and concise way to map, filter,
and flatten iterators. These examples use vec_comp!
to keep things
short and neat, but all comprehension macros use the same syntax.
With that said, a basic comprehension looks like this:
let v = vec_comp![for x in 0..10 => x];
let it = (0..10).collect::<Vec<_>>();
assert_eq!(v, it);
This will make a vector with the numbers 0 through 9… not very useful, is it? Let’s only keep the evens by adding a guard expression:
let v = vec_comp![for x in 0..10 => x, if x % 2 == 0];
let it = (0..10).filter(|x| x % 2 == 0).collect::<Vec<_>>();
assert_eq!(v, it);
Now we’re getting somewhere! You can also map the values. For example, let’s double the values:
let v = vec_comp![for x in 0..10 => x * 2, if x % 2 == 0];
let it = (0..10)
.filter(|x| x % 2 == 0)
.map(|x| x * 2)
.collect::<Vec<_>>();
assert_eq!(v, it);
Notice how the map
call comes after the filter
call in the iterator chain.
This is to show how the comprehension works: the guard applies to the input value,
not the output value.
Destructuring
Comprehensions also support destructuring, for example, tuples:
let pairs = vec![(1, 2), (3, 4), (5, 6)];
let v = vec_comp![for (x, y) in &pairs => x + y];
let it = pairs.into_iter().map(|(x, y)| x + y).collect::<Vec<_>>();
assert_eq!(v, it);
or structs:
struct Point {
x: i32,
y: i32,
}
let points = vec![Point { x: 1, y: 2 }, Point { x: 3, y: 4 }, Point { x: 5, y: 6 }];
let v = vec_comp![for Point { x, y } in &points => x + y];
let it = points.into_iter().map(|Point { x, y }| x + y).collect::<Vec<_>>();
assert_eq!(v, it);
Nesting
Nested iterators are supported up to the recursion limit:
let matrix = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
// also a good example of passing a reference to the comprehension
let v = vec_comp![for row in &matrix; for col in row => *col * 2, if *col % 2 == 0];
let it = matrix
.into_iter()
.flatten()
.filter(|x| x % 2 == 0)
.map(|x| x * 2)
.collect::<Vec<_>>();
assert_eq!(v, it);
Advanced Examples
See the iter_comp!
macro documentation for some advanced examples,
like creating a HashMap
from a comprehension.
Note on Examples
It’s important to note that iterator examples used to test the
comprehensions are equivalent to the comprehensions, but not
identical. The macros expand to nested chains of flat_map
and filter_map
calls; the examples are written for clarity
and to show the order of operations in the comprehension.
What about mapcomp
?
I’m aware of the existence of the mapcomp
crate, although it differs from this crate in a few ways. For starters,
mapcomp
aims to make their syntax as close to Python as possible and
I think they did a great job; this crate is not trying to do that. The
goal of this crate is to add comprehensions to Rust in an idiomatic way
so that the syntax flows naturally with the rest of the language while
still being concise and powerful.
On a more technical note, mapcomp
uses generators internally which was
okay for Rust 2018, but generators and yield
-ing are now experimental
features. This was a big inspiration for this crate, as I wanted to make
a macro-based solution that didn’t require nightly, so I settled on iterators
in lieu of generators.
Macros
- Generates an iterator that yields the results of the comprehension. The syntax allows for filtering, mapping, and flattening iterators (in that order).
- Convenience macro that wraps
iter_comp!
and returns aHashSet
containing the results. - Convenience macro that wraps
iter_comp!
and returns aVec
containing the results.