In addition, if the control flow of the inner expression is not exhaustive, a default case should be appended that breaks out of the loop automatically.
This would permit repeated partial match expressions that end the loop once they fail to match:
loop match value {
(Some(x), ..) | (_, Some(x), ..) | (_, _, Some(x)) => {
sum += x;
}
}
In general, this would be equivalent to:
loop {
match value {
(Some(x), ..) | (_, Some(x), ..) | (_, _, Some(x)) => {
sum += x;
}
_ => break
}
}
If the match expression is exhaustive, a value can be returned using loop_break_value. If not exhaustive, the injected break prevents this behavior.
Typically match is infallible, but a default break case is commonly useful when using match in a loop, such as to parse a value statefully. A similar existing syntax is while let, although such syntax can't be chained and only matches a single pattern.
By default, this would be equivalent to a while statement.
let mut x = 10;
loop if x != 0 {
println!("x: {x}");
x -= 1;
}
Or in general:
let mut x = 10;
loop {
if x != 0 {
println!("x: {x}");
x -= 1;
} else {
break
}
}
However, this syntax additionally permits the inclusion of an exception case using the else keyword; consistent with loop match. The else case would be included in the loop, requiring a manual break if one is desired.
let mut x = 10;
loop if x != 0 {
println!("x: {x}");
x -= 1;
} else {
println!("end");
break
}
This would be particularly useful for loop-if-else-if`-style expressions, similar to a match statement but allowing for different variables.
let mut x = 10;
let mut y = 20;
loop if x != 0 {
println!("x: {x}");
x -= 1;
} else if y != 0 {
println!("y: {y}");
y -= 1;
}
If the if-else expression is exhaustive, a value can be returned using loop_break_value. If not exhaustive, the injected break prevents this behavior.
A potential downside is that the else case could be confused for a final exception case that runs after the loop ends. This likely shouldn't be the actual behavior as to allow chained else-if looping and remain consistent with loop match, but it could seem ambiguous.
All of the related discussions propose a loop match, but they all go overboard with entirely new disparate syntax (like for match x in iter or endless unbracketed keyword chaining). They mostly just debate that other syntax.
They also generally define loop match as syntax sugar for loop { match .. }, which people either seem okay with as a convenience, or dislike it for being unnecessary. However, I think the addition of the implicit break as a default case of fallible control flow is a big difference with my proposal that makes it hold more water as a unique utility.
I would almost want to make loop if {..} else .. invalid syntax, but I feel like having exceptions to the general rule is awkward or unintuitive in its own way. I'm unsure if there's a good rule for it that wouldn't seem ambiguous. Maybe just loop match on its own would be better.
This is my thought -- but about loop match and such too.
"Yes, it needs braces. If you have so many nested loops that the indentation is high, make a function" seems like an entirely reasonable answer to me. Otherwise there's a huge trail of "but I want else loop" and such where all the same motivations hold, and I'd rather just do none of them.
Yeah I think at that point it should just be a macro with the same functionality. The syntax loop x {} seems very natural and convenient, but it seems like a bad fit for Rust after thinking about it more. I'll leave the post up for posterity or any further discussion.
let mut x = 10;
let mut y = 20;
loop if x != 0 {
println!("x: {x}");
x -= 1;
} else if y != 0 {
println!("y: {y}");
y -= 1;
}
such code is extremely dangerous, since you cannot figure that, the loop if is a infinite loop that forget a break, or a non-exaustive block that should adding an auto break.
Your code could be
loop if x != 0 {
println!("x: {x}");
x -= 1;
} else {
if y != 0 {
println!("y: {y}");
y -= 1;
}
break; // with accidently forget adding it.
}
I tried the current rust version, got
$ rustc --edition 2021 test2.rs && cat $_
warning: unreachable pattern
--> test2.rs:6:9
|
6 | _ => println!{"wtf?"}
| ^
|
= note: `#[warn(unreachable_patterns)]` on by default
warning: 1 warning emitted
fn main(){
let a:bool=true;
match a {
true => println!{"t"},
false=> println!{"f"},
_ => println!{"wtf?"}
}
if a {
println!{"t again"}
} else if !a {
println!{"f again"}
} else {
println!{"wtf??"}
}
}
It seems that, rust could detected unreachable match patterns, but cannot detect unreachable if-else patterns.
Thus with your loop if statement, some overhead might be added accidently.
And for a most dangerous thing: How could you assume the missing branch is just a break rather than doing nothing?
Suppose I have a data receiver rec, with a method receive(&'a mut self, timeout_ms:u32)->Option<&'a mut [u8]>
Then I might write code like:
loop match rec.receive(1000) {
Some(data)=>{if dealing_with(data).is_done() {break;}},
None => () // waiting for more data
}
or just
loop if let Some(data) = rec.receive(1000) {
if dealing_with(data).is_done() {
break;
}
}
In this case, how you could assume the else branch is break rather than continue?
I think I should've explained that part better in the post:
I find myself reaching for this kind of syntax a lot, particularly loop match (break as the default case of match inside a loop) and loop if else (loop with a manual break condition).
Having to stuff these in nested loops always felt a bit annoying in terms of refactoring and readability. The extra indentation felt especially unnecessary for loop { match { .. } }, and you have to shift around way more elements to convert while { .. } into loop { if { .. } else { break }. As a separate thing, this would also effectively coalesce all non-recursive looping into the loop keyword, which would seem more elegant and scannable than having a separate while keyword.
HOWEVER, I realized not too long after making the post that the implicit break is probably too confusing, especially in a systems language like Rust that tries to be very explicit and clear about the control flow for straightforward readability. Shame, but true....