11

Idiomatic for/else in rust:

In Python, I can use for/else to check whether or not a for loop terminated at a break statement or finished 'normally':

prod = 1
for i in range(1, 10):
    prod *= i
    if prod > 123:
        break
else:
    print("oops! loop terminated without break.")

Is there a similar way to do that in Rust? This is the closest I have come, but it is not particularly similar.

let mut prod = 1u64;
let mut found = false;
for i in 1..10 {
    prod *= i;
    if prod > 123 {
        found = true;
        break;
    }
}
if !found {
    println!("oops! loop terminated without break.");
}

There seems to be a discussion about this on rust internals; however, that is more about future possibilities than what is idiomatic.

In Python, idiomatic code is called pythonic - is there a similar word for idiomatic Rust code?

hiro protagonist
  • 44,693
  • 14
  • 86
  • 111
  • 1
    Not an answer, but when I face this need, I immediately think whether I really need this - or an iterator or maybe a `loop` with break-with-value will do better. – Chayim Friedman Jul 04 '22 at 08:00
  • @ChayimFriedman would you have any suggestion in this particular example (in my real case i iterate over a `Vec` and not a range)? i am not interested in the `prod`, but in the index of the vector when the product becomes too large. – hiro protagonist Jul 04 '22 at 13:12
  • I would probably use `enumerate().try_fold()`. I won't post that as an answer since the question was in general. – Chayim Friedman Jul 04 '22 at 19:53

6 Answers6

5

Since Rust 1.65, you can break out of blocks, optionally with a value. Cases like that were even mentioned in the RFC as a motivation:

let value = 'found: {
    for i in 1..2 {
        prod *= i;
        if prod > 123 {
            break 'found Some(prod);
        }
    }
    println!("oops! loop terminated without break.");
    None
};

Playground.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
4

A simple None initialized value provides a reasonably convenient way to do this:

let mut prod = 1u64;
let mut value = None;
for i in 1..10 {
    prod *= i;
    if prod > 123 {
        value = Some(prod);
        break;
    }
}
if value.is_none() {
    println!("oops! loop terminated without break.");
}
Aiden4
  • 2,504
  • 1
  • 7
  • 24
3

I don't know how idiomatic it is but sometimes I write this:

fn main() {
    let mut prod = 1u64;
    let value = 'found: loop {
        for i in 1..2 {
            prod *= i;
            if prod > 123 {
                break 'found Some(prod);
            }
        }
        println!("oops! loop terminated without break.");
        break 'found None;
    };
    //Here value is Option<u64> with the result.
}

That is, an outer labelled loop that can be broken from inside when you are ready. You can even give it a type with the break label value` syntax.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • this works. although the outer `loop` does not look that nice. even if it never really loops... – hiro protagonist Jul 04 '22 at 07:27
  • @hiroprotagonist: I agree it is not ideal. I tend to forget the ending `break` and have an infinite loop. – rodrigo Jul 04 '22 at 07:32
  • clippy agrees with both of us: `error: this loop never actually loops`. had to decorate it with `#[allow(clippy::never_loop)]`. – hiro protagonist Jul 04 '22 at 07:35
  • @hiroprotagonist: If you don't need the break value you can loop with `for _ in [()]`, but this time Rustc itself complains with: "warning: for loop over a single element". – rodrigo Jul 04 '22 at 07:40
  • 2
    [label-break-value](https://github.com/rust-lang/rust/issues/48594). [Example](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=831005fdea0499a2dfe33c21f3a86d4b). – Chayim Friedman Jul 04 '22 at 07:59
3

Usually you can translate to some iterator pattern instead of the for loop:

fn main() {
    let mut prod = 1u64;
    if let Some(i) = (1..10)
        .filter_map(|i| {
            prod *= i;
            if prod > 123 {
                Some(i)
            } else {
                None
            }
        })
        .next()
    {
        println!("item found for {i}");
    }
}

Playground

Netwave
  • 40,134
  • 6
  • 50
  • 93
2

I have seen people use a temporal locale function, Javascript style:

fn main() {
    let mut prod = 1u64;
    let value = (|| {
        for i in 1..10 {
            prod *= i;
            if prod > 123 {
                return Some(prod);
            }
        }
        println!("oops! loop terminated without break.");
        None
    })();
}

You define a temporary function and immediately call it, this way you can have return inside. This trick is also used to propagate errors with ?, while the try {} statement is not stable.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
-5

How about this?

let result = {
    for i in 1..10 {
        prod *= i;
        if prod > 123 {
            Ok(())
        }
    }
    Err(())
}
Aron
  • 15,464
  • 3
  • 31
  • 64