lang (syntax | expressions | control-flow)
Allow a break
of labelled blocks with no loop, which can carry a value.
In its simplest form, this allows you to terminate a block early, the same way that return
allows you to terminate a function early.
'block: {
do_thing();
if condition_not_met() {
break 'block;
}
do_next_thing();
if condition_not_met() {
break 'block;
}
do_last_thing();
}
In the same manner as return
and the labelled loop breaks in RFC 1624, this break
can carry a value:
let result = 'block: {
if foo() { break 'block 1; }
if bar() { break 'block 2; }
3
};
RFC 1624 opted not to allow options to be returned from for
or while
loops, since no good option could be found for the syntax, and it was hard to do it in a natural way. This proposal gives us a natural way to handle such loops with no changes to their syntax:
let result = 'block: {
for &v in container.iter() {
if v > 0 { break 'block v; }
}
0
};
This extension handles searches more complex than loops in the same way:
let result = 'block: {
for &v in first_container.iter() {
if v > 0 { break 'block v; }
}
for &v in second_container.iter() {
if v < 0 { break 'block v; }
}
0
};
Implementing this without a labelled break is much less clear:
let mut result = None;
for &v in first_container.iter() {
if v > 0 {
result = Some(v);
break;
}
}
if result.is_none() {
for &v in second_container.iter() {
if v < 0 {
result = Some(v);
break;
}
}
}
let result = result.unwrap_or(0);
'BLOCK_LABEL: { EXPR }
would simply be syntactic sugar for
'BLOCK_LABEL: loop { break { EXPR } }
except that unlabelled break
s or continue
s which would bind to the implicit loop
are forbidden inside the EXPR.
This is perhaps not a conceptually simpler thing, but it has the advantage that all of the wrinkles are already well understood as a result of the work that went into RFC 1624. If EXPR contains explicit break
statements as well as the implicit one, the compiler must be able to infer a single concrete type from the expressions in all of these break
statements, including the whole of EXPR; this concrete type will be the type of the expression that the labelled block represents.
Because the target of the break
is ambiguous, code like the following will produce an error at compile time:
loop {
'labelled_block: {
if condition() {
break;
}
}
}
If the intended target of the break
is the surrounding loop, it may not be clear to the user how to express that. Where there is a surrounding loop, the error message should explicitly suggest labelling the loop so that the break
can target it.
'loop_label: loop {
'labelled_block: {
if condition() {
break 'loop_label;
}
}
}
This can be taught alongside loop-based examples of labelled breaks.
The proposal adds new syntax to blocks, requiring updates to parsers and possibly syntax highlighters.
Everything that can be done with this feature can be done without it. However in my own code, I often find myself breaking something out into a function simply in order to return early, and the accompanying verbosity of passing parameters and return values with full type signatures is a real cost.
Another alternative would be to revisit one of the proposals to add syntax to for
and while
.
We have three options for handling an unlabelled break
or continue
inside a labelled block:
break
and continue
break
to the labelled block, compile error on continue
break
and continue
through the labelled block to a containing loop
/while
/for
This RFC chooses the first option since it's the most conservative, in that it would be possible to switch to a different behaviour later without breaking working programs. The second is the simplest, but makes a large difference between labelled and unlabelled blocks, and means that a program might label a block without ever explicitly referring to that label just for this change in behavior. The third is consistent with unlabelled blocks and with Java, but seems like a rich potential source of confusion.
None outstanding that I know about.