Crate r4 [] [src]

Provides a macro that generates an iterable sequence via for comprehensions (AKA list comprehensions).

If you're familiar with Python's list comprehensions or Scala's for comprehensions, this should be familiar. The use of the yield keyword is also similar to that of the recent generator syntax RFC.

Examples

Sequences are built out of for statements, which may be nested.

#[macro_use(iterate)] extern crate r4;

let v: Vec<i32> = iterate![for x in 0..5; yield x * 2].collect();
assert_eq!(v, vec![0, 2, 4, 6, 8]);

Multiple for statements are treated like nested loops.

#[derive(Debug, Eq, PartialEq)]
struct Item { x: i32, y: i32, }

let v: Vec<Item> = iterate![for x in 0..3;
                            for y in 5..7;
                            yield Item { x: x, y: y, }]
                   .collect();

let mut w: Vec<Item> = Vec::new();
for x in 0..3 {
  for y in 5..7 {
    w.push(Item { x: x, y: y, });
  }
}
assert_eq!(v, w);

Stand-alone values may be yielded.

let v: Vec<i32> = iterate![yield 0; yield 2; yield 4; yield 6; yield 8].collect();
assert_eq!(v, vec![0, 2, 4, 6, 8]);

Intermediate variables may be bound with let, which may take a pattern.

let v: Vec<i32> = iterate![for x in 0..6; let y = x * 2; yield x * y].collect();
assert_eq!(v, vec![0, 2, 8, 18, 32, 50]);

struct Item { x: i32, y: i32, }
let v: Vec<i32> = iterate![for x in 0..3;
                           for y in 5..7;
                           let item = Item { x: x, y: y, };
                           let Item { x: a, y: b, } = item;
                           yield a;
                           yield b]
                  .collect();
assert_eq!(v, vec![0, 5, 0, 6, 1, 5, 1, 6, 2, 5, 2, 6]);

Iteration may be short-circuited via if.

let v: Vec<i32> = iterate![for x in 0..10; if x % 2 == 0; yield x].collect();
assert_eq!(v, vec![0, 2, 4, 6, 8]);

A pattern-based guard may be employed via if let.

fn process(a: i32) -> Option<i32> {
  if a % 2 == 0 {
    Some(a)
  } else {
    None
  }
}

let v: Vec<i32> = iterate![for x in 0..10;
                           if let Some(y) = process(x);
                           yield y]
                  .collect();
assert_eq!(v, vec![0, 2, 4, 6, 8]);

Expressions

See the macro for a full breakdown of expressions it recognizes. In brief, they are:

  • for x in xs: introduces a new scope that iterates over xs, binding the pattern x to each of its values.
  • if cond: short-circuits subsequent expressions if cond is not true.
  • if let pattern = expr: introduces a new scope that binds pattern to expr, short-circuiting subsequent expressions if pattern does not match expr.
  • let a = b: introduces a new scope that binds a (which may be a pattern) to b.
  • yield r: emits the value of the expression r.

Example expansion

This macro invocation:

#[macro_use(iterate)] extern crate r4;
let items = iterate![for x in 0..10;
                     let y = x + 1;
                     if y % 2 == 0;
                     let z = y + 3;
                     for x_prime in z..(z + 3);
                     yield x_prime];

is expanded like so:

extern crate r4;
let items = {
  (0..10).flat_map(move |x| {
    let y = x + 1;
    r4::FlatIter::new(if y % 2 == 0 {
                        Some({ let z = y + 3;
                               (z..(z + 3)).flat_map(move |x_prime| {
                                                       ::std::iter::once(x_prime)
                                                     })
                             })
                      } else {
                        None
                      })
  })
};

Lifetimes and moved values

You can see in the example expansion above that the closures which iterate! creates move variables that they refer to out of their surrounding environment. This is done for lack of a better mechanism for ensuring that the iterator created by iterate! doesn't outlive values referred to by the closures it generates. This makes it possible for an iterate!-derived macro to own its data, as in:

fn generate_values() -> Box<Iterator<Item=u32>> {
  let xs = vec![1, 2, 3, 4];
  Box::new(iterate![for x in 0..10; if xs.contains(&x); yield 2 * x])
}

As a result, if you refer to a non-Copy value from within an iterate! macro, you will no longer be able to use that value.

Be careful when using this code, it's not being tested!
fn generate_values() -> Box<Iterator<Item=u32>> {
  let xs = vec![1, 2, 3, 4];
  let i = iterate![for x in 0..10; if xs.contains(&x); yield 2 * x];
  // We already moved `xs` into the iterator above, so we can't use it again.
  println!("xs are: {:?}", xs);  // Compilation error.
  Box::new(i)
}

This behavior can be circumvented by creating a borrow yourself, although this will restrict the lifetime of the iterator to that of the borrow.

let xs = vec![1, 2, 3, 4];
let borrow = &xs;
// The iterators `i` and `j` are both limited to the lifetime of `borrow`,
// but they can share use of its data.
let i = iterate![for x in 0..10; if borrow.contains(&x); yield 2 * x];
let j = iterate![for x in 2..20; if borrow.contains(&x); yield 2 * x];
let v: Vec<i32> = i.chain(j).collect();
assert_eq!(v, vec![2, 4, 6, 8, 4, 6, 8]);

Macros

iterate

Produces an iterator over values yielded in a sequence of semicolon-separated expressions.

Structs

FlatIter

Flat-maps an Option<I> where I: Iterator into the underlying iterator. You probably don't need to use this type directly. It is used internally by the iterate! macro.