#step #macro #condition #loops #c-style #continue #initialization

cfor

A macro that gives Rust a C-style for loop, with initialisation, condition and step. This correctly handles control-flow like continue and break.

6 releases (2 stable)

Uses old Rust 2015

1.1.0 Nov 14, 2015
1.0.0 May 25, 2015
0.2.1 Mar 2, 2015
0.2.0 Jan 20, 2015
0.1.1 Nov 14, 2014

#2044 in Rust patterns

Download history 11/week @ 2024-02-16 17/week @ 2024-02-23 12/week @ 2024-03-01 15/week @ 2024-03-08 15/week @ 2024-03-15 16/week @ 2024-03-22 38/week @ 2024-03-29

86 downloads per month
Used in 2 crates

MIT/Apache

12KB

Contains (ELF exe/lib, 7KB) a.out

A C-style for loop for Rust

Build Status Coverage Status

A Rust macro implementing a C-style for loop. See the docs for more information.


lib.rs:

A C-style for loop in macro form.

This takes the form cfor!(initialiser; condition; step { body }).

  • initialiser is a statement evaluated before any iterations of the loop. Any variables declared here are scoped to the cfor! invocation, that is, only usable inside condition, step and body.
  • condition is an boolean expression evaluated at the start of each iteration. If it evaluates to false iteration will stop.
  • step is an arbitrary expression which is executed at the end of each iteration (including if continue is called), before condition is checked.

The initialiser and condition can be empty like C, but the step cannot unlike C. A for loop with no step is identical to a while loop.

Source & issue tracker

When should I use it?

Only when cfor! is clearer than the more declarative built-in iterators, their adaptors and the for loop. For example, the built-in iterator functionality is more self-contained so there is less risk of accidentally writing i in a condition when j was meant (I personally get bitten by this semiregularly when writing nested "2D" loops in C).

Furthermore, the adaptor methods linked above allow one to write concise, performant, reusable "functional" code in a way that is not possible to achieve using C-style iteration.

How to use it?

Add the repository as a normal cargo dependency, and include into your crate with #[phase(plugin)]. (See examples below.)

[dependencies.cfor]
cfor = "1.0"

Examples

Simple

A non-additive condition is not handled extremely naturally by std::iter, but is straight-forward to handle directly.

#[macro_use] extern crate cfor;

fn main() {
    cfor!{let mut x = 1; x < 0x1000; x *= 2; {
        println!("power of 2: {}", x);
    }}
}

Intrabody condition

If a condition requires some extra computation to be checked (or if there is some code that should always be evaluated, even if the condition will be false for a given iteration), the condition in the cfor header can be omitted.

#[macro_use] extern crate cfor;

fn main() {
    cfor!{let mut x = 1; ; x *= 2; {
        // ... setup ...
        println!("handling power of 2: {}", x);

        if x < 0x1000 { break }

        // ... further handling ...
        println!("handling power of 2: {}", x);
    }}
}

Out-of-loop initialisation

Sometimes one may wish to have access to a variable outside the loop after it finishes so it has to be declared outside the loop, or one may be iterating over some presupplied/-computed value so there is no meaningful additional initialisation possible. The initialisation expression can be safely omitted in this case.

#[macro_use] extern crate cfor;

extern crate rand;

fn main() {
    let mut x = 1u16;

    cfor!{; x < 0x1000; x *= 2; {
        println!("power of 2: {}", x);

        // sometimes quit early
        if x > rand::random() { break }
    }}

    println!("actually stopped at {}", x);
}

Handling continue

(Or, "why is the macro so complicated?")

Special effort is made to ensure that continue acts correctly, a naive macro defined as follows will cause continue to also skip evaluating step, likely leading to undesirable behaviour like infinite loops.

// WARNING: this is broken.
macro_rules! bad_cfor {
    ($init: stmt; $cond: expr; $step: expr; $body: block) => {
        {
            $init;
            while $cond {
                $body;

                $step;
            }
        }
    }
}

fn main() {
    let mut true_counter = 0;

    bad_cfor!{let mut i = 0; i < 10; i += 1; {

        // manually avoid the infinite loop
        if true_counter >= 50 { break }
        true_counter += 1;

        println!("i = {}", i);
        // try to skip just i == 4
        if i == 4 {
            // but this skips the i += 1 leaving us
            // on i == 4 forever.
            continue
        }
        // ...more code...
    }}
}

This is invoked in the same manner as cfor!, but, if $body contains a continue, the $step at the end of the loop body will never be evaluated.

Handling multiple initializations and steps

Like C loops, cfor! supports specfying multiple initializations and steps seperated by a comma.

#[macro_use] extern crate cfor;

fn main() {
    cfor!{let mut x = 0, let mut y = x; x <= 10 && y <= 100; x += 1, y += 10; {
        println!("x: {}, y: {}", x, y);
    }}
}

No runtime deps