Crate rustcomp

source ·
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 a HashSet containing the results.
  • Convenience macro that wraps iter_comp! and returns a Vec containing the results.