6.18. do-while Loops
Don't use do...while loops .
Like any other postfix looping construct, a do...while loop is intrinsically hard to read, because it places the controlling condition at the end of the loop, rather than at the beginning.More importantly, in Perl a do...while loop isn't a "first-class" loop at all. Specifically, you can't use the next, last, or redo commands within a do...while. Or, worse still, you can use those control directives; they just won't do what you expect.For example, the following code looks like it should work:
sub get_big_int {
my $int;
TRY:
do {
# Request an integer...
print 'Enter a large integer: ';
$int = <>;
# That's not an integer!...
next TRY if $int !~ /\A [-+]? \d+ \n? \z/xms;
# Otherwise tidy it up a little...
chomp $int;
} while $int < 10; # Until the input is more than a single digit
return $int;
}
# and later...
for (1..$MAX_NUMBER_OF_ATTEMPTS) {
print sqrt get_big_int( ), "\n";
}
That looks okay, but it isn't. Specifically, if a non-integer is ever entered and the next trY command is invoked, that next starts looking for an appropriately labeled loop to re-iterate. But the do...while isn't actually a loop; it's a postfix-modified do block. So the next ignores the trY: label attached to the do. Control passes out of the do block, and then out of the subroutine call (a subroutine isn't a loop either), until it returns to the for loop. But the for loop isn't labeled trY:, so control passes outwards again, this time right out of the program.In other words, if the user ever enters a value that isn't a pure integer, the entire application will immediately terminatenot a very robust or graceful way to respond to errors. That kind of bug is particularly hard to find too, because it's one of those rare cases of a Perl construct not doing what you mean. It looks right, but it works wrong.The best practice is to avoid do...while entirely. The simple way to do that is to use a regular while loop instead, but to "counter-initialize" the $int variable, to guarantee that the loop executes at least once:
sub get_big_int {
my $int = 0; # Small value so the while loop has to iterate at least once
TRY:
while ($int < 10) {
print 'Enter a large integer: ';
$int = <>;
next TRY if $int !~ /\A [-+]? \d+ \n? \z/xms;
chomp $int;
}
return $int;
}
Sometimes, however, the condition to be met is too complex to permit counter-initialization, or perhaps no counter-initial value is possible. This most commonly occurs when the test is performed by a separate subroutine. In such cases, either use a flag:
sub get_big_int {
my $tried = 0;
my $int;
while (!$tried || !is_big($int)) {
print 'Enter a valid integer: ';
$int = <>;
chomp $int;
$tried = 1;
}
return $int;
}
or, better still, use a return to explicitly escape from an infinite loop:
sub get_big_int {
while (1) {
print 'Enter a valid integer: ';
my $int = <>;
chomp $int;
return $int if is_big($int);
}
return;
}