Breaking out is hard to do
source link: http://smallcultfollowing.com/babysteps/blog/2012/02/02/breaking-out-is-hard-to-do/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Breaking out is hard to do
Feb 2, 2012
One of the things I’d like to do for the iteration library is settle on a convention for breaking and continuing within loops. There is a bug on this issue (#1619) and it seems like the general approach is clear but some of the particulars are less so. So I thought I’d try to enumerate how code will look under the various alternatives and then maybe we can settle on one: they’re all fairly similar. Who knows, maybe just writing things out will settle my mind.
Alternative #1: The loop_ctl
type.
This was my original proposal. Basically, there will be a type
called iter::loop_ctl
defined as so:
enum loop_ctl { lc_break, lc_cont }
I wanted to design something that felt as much like normal loops as
possible. So my thought was that, for sugared closures where the
return type was loop_ctl
, the compiler could insert lc_cont
as the
tail expression should there not be one already.
The idea then was to change vec::iter()
from a function with the signature:
fn iter<T>(v: [T], f: fn(T))
to the following:
fn iter<T>(v: [T], f: fn(T) -> loop_ctl)
In other words, the function supplied to iter
would be allowed to
break the loop in the middle if it wanted to. Due to the default
rules, this is mostly invisible, except that you can say break
and
cont
and things work as you expect.
Unfortunately, as I think more about it, I realize that the default rules aren’t quite subtle enough. It’s only mostly invisible. For example:
vec::iter(v) {
while cond { }
}
This would fail because while loops have a result type of ()
, and in
this case the while
loop occupies the tail expression slot. So you would
have to write:
vec::iter(v) {
while cond { }
cont;
}
This makes me unhappy. I’m happy with smart rules but only if they
really work all the time or have a consistent story. You could extend
the rule to say “if the tail expression has unit type, still insert a
default cont
”, but now it’s starting to sound really magical.
Alternative #2:
We can keep the loop_ctl
type but just not make it special. Iterable
types define two methods, iter
and iter_brk
(not sure about those names),
with signatures as shown (these are for vectors:
iface iterable<T> {
fn iter(f: fn(T)) /* as today */
fn iter_brk(f: fn(T) -> loop_ctl)
}
Now when you want to break, you have to end the loop explicitly with cont
.
For most types, you need only define iter_brk
: the iter
function
itself can be defined generically as shown (this assumes traits are
implemented):
trait base_iter<T> {
req fn iter_brk(f: fn(T) -> loop_ctl);
fn iter(f: fn(A)) {
self.iter_brk {|e|
f(e);
cont;
}
}
}
Alternative #3:
Same as #2, but we replace the loop_ctl
type with boolean. This is
appealing because it’s so minimalistic. break
would effectively
return false
and cont
would return true
. This makes iter_brk
effectively the same as the predicate test all()
, which returns true
if the block returns true for all members.
Of course, if we actually used all()
instead of iter_brk
that’d be
ok too, except that the return type of all is bool
, so a semicolon would
be required:
v.all {|i|
...
};
We could of course have both all
and iter_brk
(as we would in
alternative #2).
My preference?
I started out liking alternative #1, but writing this blog post has
more-or-less persuaded me that I prefer alternative #2. Less compiler
magic is good, and compiler magic that fails is bad. Between
alternatives #2 and #3, I tend to slightly prefer an explicit
loop_ctl
type over a boolean for a couple of reasons:
- the types more closely reflect the intention. To me, testing whether a predicate holds on all members is not the same as interrupting a loop early.
- you can’t use
break
andcont
to return out of arbitrary blocks that happen to return boolean. -
you can always write helpers like
fn break_if(b: bool) -> loop_ctl { if b { lc_break } else { lc_cont } } fn cont_if(b: bool) -> loop_ctl { break_if(!b) }
to convert between
bool
andloop_ctl
when convenient.
But obviously there is no substantive difference between alternatives #2 and #3.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK