#const #loops #const-fn #for #range-iterator

build const_for

For loop in const implemented with a macro

5 releases

0.1.4 Jan 22, 2024
0.1.3 Nov 4, 2023
0.1.2 Mar 19, 2023
0.1.1 Mar 10, 2023
0.1.0 Mar 10, 2023

#66 in Build Utils

Download history 518/week @ 2024-03-13 930/week @ 2024-03-20 688/week @ 2024-03-27 612/week @ 2024-04-03 545/week @ 2024-04-10 1061/week @ 2024-04-17 694/week @ 2024-04-24 389/week @ 2024-05-01 672/week @ 2024-05-08 578/week @ 2024-05-15 519/week @ 2024-05-22 1413/week @ 2024-05-29 1297/week @ 2024-06-05 1311/week @ 2024-06-12 2448/week @ 2024-06-19 3142/week @ 2024-06-26

8,595 downloads per month
Used in 5 crates (2 directly)

MIT license

17KB
55 lines

const_for

Const for

GitHub crates.io Docs

Regular for loops are not allowed in const contexts, because it relies on iterators, which are not available in const.
This is rather annoying when writing const functions, as you need to write custom for loops using 'loop' or 'while'.

This crate provides a macro implementation of a for loop over a range that is usable in const contexts.
The aim is to imitate a regular for loop as closely as possible. It handles break and continue correctly, and the variable is immutable in the body.
To make the for loop as versatile as possible, it comes with macro variants to handle .rev() and step_by(x), which imitates the respective function calls. This is necessary, as normally they depend on non-const iterators. But they can be used here with identical syntax.

The main restriction is that the macro only supports standard(exclusive) ranges, eg. 0..10 and -5..5, but not ..5 or 0..=10. This is mostly a limit of current stable Rust, and wont be possible without using nightly before #![feature(const_range_bounds)] becomes stable.

let mut a = 0;
const_for!(i in 0..5 => {
    a += i
});
assert!(a == 10)

This is equivalent to the following regular for loop, except it is usable in const context.

let mut a = 0;
for i in 0..5 {
    a += i
}
assert!(a == 10)

Custom step size

A custom step size can be set:

let mut v = Vec::new();
const_for!(i in (0..5).step_by(2) => {
    v.push(i)
});
assert!(v == vec![0, 2, 4])

The loop behaves as if the function was called on the range, but it is implemented by a macro.
It is equivalent to the following non-const loop:

let mut v = Vec::new();
for i in (0..5).step_by(2) {
    v.push(i)
}
assert!(v == vec![0, 2, 4])

Reversed

Iteration can be reversed:

let mut v = Vec::new();
const_for!(i in (0..5).rev() => {
    v.push(i)
});
assert!(v == vec![4, 3, 2, 1, 0])

The loop behaves as if the function was called on the range, but it is implemented by a macro.
It is equivalent to the following non-const loop:

let mut v = Vec::new();
for i in (0..5).rev() {
    v.push(i)
}
assert!(v == vec![4, 3, 2, 1, 0])

Reversed and custom step size

It is possible to combine rev and step_by, but each can only be appended once. So the following two examples are the only legal combinations.

// Reverse, then change step size
let mut v = Vec::new();
const_for!(i in (0..10).rev().step_by(4) => {
    v.push(i)
});
assert!(v == vec![9, 5, 1]);

// Change step size, then reverse
let mut v = Vec::new();
const_for!(i in (0..10).step_by(4).rev() => {
    v.push(i)
});
assert!(v == vec![8, 4, 0])

Notes

You can use mutable and wildcard variables as the loop variable, and they act as expected.

// Mutable variable
let mut v = Vec::new();
const_for!(mut i in (0..4) => {
    i *= 2;
    v.push(i)
});
assert!(v == vec![0, 2, 4, 6]);

// Wildcard variable
let mut a = 0;
const_for!(_ in 0..5 =>
   a += 1
);
assert!(a == 5)

The body of the loop can be any statement. This means that the following is legal, even though it is not in a regular for loop.

let mut a = 0;
const_for!(_ in 0..5 => a += 1);

unsafe fn unsafe_function() {}
const_for!(_ in 0..5 => unsafe {
   unsafe_function()
});

If the beginning of the range plus the step overflows the integer, behaviour is undefined.

Real world example

Here is an example of how this crate helped make some actual code much nicer and readable.

The code was taken (and edited a bit for clarity) from the Cadabra chess engine.

Before:

const fn gen_white_pawn_attacks() -> [u64; 64] {
    let mut masks = [0; 64];

    let mut rank: u8 = 0;
    while rank < 8 {
        let mut file: u8 = 0;
        while file < 8 {
            let index = (rank*8+file) as usize;
            if file != 7 { masks[index] |= (1 << index) >> 7 as u64 }
            if file != 0 { masks[index] |= (1 << index) >> 9 as u64 }

            file += 1;
        }
        rank += 1;
    }

    masks
}

After:

const fn gen_white_pawn_attacks() -> [u64; 64] {
    let mut masks = [0; 64];

    const_for!(rank in 0..8 => {
        const_for!(file in 0..8 => {
            let index = (rank*8+file) as usize;
            if file != 7 { masks[index] |= (1 << index) >> 7 as u64 }
            if file != 0 { masks[index] |= (1 << index) >> 9 as u64 }
        })
    });

    masks
}

License: MIT

No runtime deps