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....
Sometimes you just cannot make a function, because it would be passing like 10 arguments, especially in embedded, where you are trying to pass all resources manually, without using allocator to get some memory. It's really very common