D gives the possibility of multiple-breaking out (nested) while/for blocks using labeled breaks.
I think it would be beneficial to extend this to being able to break out of any "block", as long as it is labeled:
----
void main()
{
label: {
// ..code..
writefln("code1");
writefln("break..");
break label; // jumps to "here:"
// ..code..
writefln("code2");
}
// break lands here
writefln("here");
}
----
The main use case here would actually be to replace the "triangle if" pattern:
----
if(condition1)
{
if(condition2)
{
if(condition3)
{
if(condition4)
{
//DO SOMETHING
}
}
}
}
----
Which would become
----
conditional_block:
{
if(!condition1) break conditional_block;
if(!condition2) break conditional_block;
if(!condition3) break conditional_block;
if(!condition4) break conditional_block;
//DO SOMETHING
}
----
The usual "workaround" to the "triangle if" pattern is to move it to it's own function, and *return* from it. The disadvantage is that it burdens the developer with a whole extra function. The "breakable block" has the advantage of not breaking the logic flow of the current function.
Another workaround, the "do_while_false" is also possible:
----
do:
{
if(!condition1) break;
if(!condition2) break;
if(!condition3) break;
if(!condition4) break;
//DO SOMETHING
}while(false);
----
However, my experience is that this pattern tends to be confusing for readers that expected a loop: The actual condition "while(false)" is only documented near the end.
Further more, given both the above workarounds, neither scale should you want to nest a double break.
Conclusion: Generic breakable labeled blocks. It works better and is clearer/more verbose.
Thank you/
Comment #1 by andrei — 2012-09-05T07:59:03Z
This is a sensible feature. However, it has several workarounds and doesn't enable new patterns. I think we should make additions to the language only if they have major usefulness.
Comment #2 by monarchdodra — 2012-09-05T08:31:06Z
Got it. Thanks.
Comment #3 by ds.dlang — 2012-12-19T07:38:20Z
This feature is related to an "expectation bug", in that current spec is very counterintuitive regarding labeled blocks. I added a couple of "See Also" bugs where the current behavior of labeled blocks has tripped people up. The problems are best demonstrated by a code sample (taken from those issues):
// Problem 1 (not a bug according to spec, but entirely counterintuitive).
int n;
label:
{
scope(exit) n++;
n=3;
}
writefln("n=%d should be 4", n); // Prints "n=3 should be 4"
// Problem 2: (counterintuitive AND a bug because should not compile).
block:
{
for (int i = 0; i < 10; i++) {
if (i == 5) break block;
}
writefln("Within block"); // This line IS printed.
}
writefln("Outside block");
The common problem for both cases is that a labeled block adds curly braces, but the code behaves as if the braces weren't there.
In particular, adding a label to a block makes the block's scope disappear. Consider how the first example would change if "label:" line is commented out.
That trips people up. It could be solved perhaps by simply prohibiting labels on a block, but monarchdodra's suggestion seems like an really nice solution, allowing for very intuitive flexibility (e.g. the second example is equivalent to python's "else" clause on loops, more intuitive, and nearly as concise).
(Note that monarchdodra's suggestion critically depends on labeled blocks. Unlabeled breaks definitely need to break out of the innermost loop or switch.)
Comment #4 by smjg — 2013-05-29T13:49:15Z
(In reply to comment #1)
> This is a sensible feature. However, it has several workarounds and doesn't
> enable new patterns. I think we should make additions to the language only if
> they have major usefulness.
Implementing this isn't really an addition to the language, it's the removal of
a restriction.
Comment #5 by andrei — 2016-10-15T17:34:45Z
Please formulate this as a DIP if there's interest.
Comment #6 by nick — 2016-10-16T19:41:14Z
An alternate syntax which supports `break` without a label is to extend switch:
switch {
auto x = foo();
if (x == bar) break;
auto y = x.baz();
if (!y) break;
y.writeln;
}
Here the switch condition defaults to true, and `default:` is implied.