2

In C, a for loop has an optional increment section which I sometimes miss in Rust:

for (uint i = 0; i < max; i = step_function(i, j, k)) {
    /* many lines of code! */
}

This could be written in Rust as:

let mut i: u32 = 0;
while (i < max) {
    // 
    // many lines of code! 
    //
    i = step_function(i, j, k);
}

... however this will introduce bugs if continue exists somewhere in the "many lines of code". My personal preference is also to keep the increment at the top of the loop.

Without creating a special iterator to handle this, is there a way to loop that matches C style more closely, accounting for both issues mentioned?

By "special iterator", I mean not having to define an iterator type and methods outside the for loop.

While it may seem like an artificial requirement, having to define an iterator for a single use - adds some overhead both in reading and writing the code.

Although @kennytm's answer shows how a reusable StepByFn iterator could work, using closures adds some constraints to the code that wouldn't exist otherwise.

Jonas
  • 121,568
  • 97
  • 310
  • 388
ideasman42
  • 42,413
  • 44
  • 197
  • 320
  • I'm not sure if this is really a dupe, but http://stackoverflow.com/questions/27893223/how-do-i-iterate-over-a-range-with-a-custom-step gives you the answer. – Rakete1111 May 06 '17 at 17:03
  • And the related [What is a stable way to iterate on a range with custom step?](http://stackoverflow.com/q/31371478/155423). – Shepmaster May 06 '17 at 17:04
  • 2
    This example isn't using a custom step size, its stepping by an arbitrary function, making the examples above not so closely related. (`step_by` couldn't be used to replace `step_function` usage here). – ideasman42 May 06 '17 at 18:16
  • 1
    What kind of constraint you need that a closure cannot cover? i.e. do you have some concrete example? – kennytm May 08 '17 at 07:48
  • @kennytm, from using closures in the past - there are some expressions you can use in the body of a block which will give errors in a closure. Doubtless there are ways to workaround most cases - however this makes porting code a less straightforward process. Hence asking if C-style loops are possible. Of course there are pros/cons with using a macro (the `cfor` crate), so solutions need to be weighted up - perhaps to get an initial port of C code, using `cfor` macro makes sense for eg, later the code can be made more *rustic* afterwards *(once tests are in place)*. – ideasman42 May 11 '17 at 00:23

1 Answers1

4

If you could import an external crate, you should use itertools::iterate:

extern crate itertools;
use itertools::iterate;

fn main() {
    for i in iterate(0, |i| 2*i + 3).take_while(|i| *i < 100) {
        println!("{}", i);
        // 0 3 9 21 45 93
    }
}

And if you are really missing the C-style for loop, you could use the cfor crate:

#[macro_use] extern crate cfor;

fn main() {
    cfor!{ let mut i = 0; i < 100; i = 2*i + 3; {
        println!("{}", i);
        // 0 3 9 21 45 93
    }}
}

If you are restricting to using the standard library only, creating a special iterator would be the most idiomatic way.

fn main() {
    for i in StepByFn::new(0, 100, |i| 2*i + 3) {
        println!("{}", i);
        // 0 3 9 21 45 93
    }
}

struct StepByFn<T, F> {
    begin: T,
    end: T,
    step: F,
}

impl<T, F: FnMut(&T) -> T> StepByFn<T, F> {
    pub fn new(begin: T, end: T, step: F) -> StepByFn<T, F> {
        StepByFn { begin, end, step }
    }
}

impl<T: PartialOrd, F: FnMut(&T) -> T> Iterator for StepByFn<T, F> {
    type Item = T;
    fn next(&mut self) -> Option<T> {
        if self.begin >= self.end {
            return None;
        }
        let next = (self.step)(&self.begin);
        let prev = std::mem::replace(&mut self.begin, next);
        Some(prev)
    }
}

It is also possible to create an inline iterator with repeat().scan(), but it is very ugly and does not express the intention very well

use std::iter::repeat;

fn main() {
    for i in repeat(()).scan(0, |i, ()| { 
        let old = *i; 
        *i = 2*old + 3; 
        if old < 100 { Some(old) } else { None } 
    }) {
        println!("{}", i);
        // 0 3 9 21 45 93
    }
}
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • While I agree with the content of your answer, I don't believe this answers the question with the restrictions the OP has required: *Without creating a special iterator*. Additionally, since OP has underspecified the problem, `j` and `k` might be calculated *inside* the loop (maybe from an external source, even). All the solutions presented here require all the information on how much to iterate to be known before starting the loop. – Shepmaster May 07 '17 at 13:20
  • @Shepmaster ① If `iterate().take_while()` or `repeat().scan()` count as "creating a special iterator", then `Range::step_by` is also a special iterator. (BTW [not strictly following OP's restriction is acceptable](http://stackoverflow.com/help/how-to-answer). And this should be more helpful for future readers.) ② Recalculating `j` and `k` means making them a `Cell`. I'll wait for OP to clarify. ③ I don't see what you mean by "information on how much to iterate". – kennytm May 07 '17 at 13:34
  • *this should be more helpful for future readers* — I **absolutely agree**. I'm just pointing out the nuances in the OP. I honestly don't know if simply instantiating an iterator would meet the restrictions or not. *information on how much to iterate* was me trying to be abstract about "all the things that go into the closure". In your examples, that would be `2*i + 3` but with the potential complication of external data and friends. – Shepmaster May 07 '17 at 13:43
  • @Shepmaster I see, thanks. If the `step_function` is simple enough (i.e. it doesn't depend on `j` and `k` that needs to be recalculated *inside* the loop), the current approach should be enough. Too bad `scopeguard::defer!` doesn't work here :( – kennytm May 07 '17 at 13:47
  • @Shepmaster: Specifying "not using an iterator" is an X/Y issue. Let the OP expose the problem, and the answers propose solutions. Restrictions of the kind "not using semi-colons", "not using braces" or "not creating a custom iterator"? Why? We're not [Perec](https://fr.wikipedia.org/wiki/Georges_Perec). – Matthieu M. May 07 '17 at 16:13
  • @MatthieuM. my only point is that *the OP* specifically says "without creating a special iterator", thus my comment is only a warning that the OP might dislike this answer which proposes creating a special iterator. I think it's a poor restriction as well, but posters have strange whims. In the general case, there are reasons to have restrictions in questions. I'd be frustrated if answerers disregarded my restrictions and answered otherwise. If we were doing that, we could do away with the restriction of "the loop iterates a different amount each time" and just mark it as a duplicate ;-) – Shepmaster May 07 '17 at 21:22
  • Edited the question, by creating a special iterator I meant having to define a new iterator type for each for loop. Mainly because this is _not_ zero overhead in terms of reading/writing code. Also closures impose some constraints wrt scope & function use. – ideasman42 May 08 '17 at 00:27