Perl Part I · Module 9 · Loops
do-while and do-until
Every loop so far tests its condition before running the body, which means a condition that is false at the start produces zero iterations. Sometimes that is wrong: a retry has to attempt something before it can know whether to retry, and a read-until-sentinel has to read before it can check for the sentinel. The do-while form moves the test to the bottom, so the body always runs at least once. It is the least used loop form in Perl and the one with the sharpest restriction, because it is not implemented as a loop at all — it is a block with a condition attached — and the two keywords you would reach for to control it are not merely ineffective inside it, they are fatal.
Intermediate13 min readPerlPart IControl FlowLoops
Part I · Module 9 · Chapter 9.4 · do-while and do-until
1. The Body Runs First
use strict;
use warnings;
my $i = 10;
do { print "do-while body ran, i=$i\n" } while ($i < 5);
my $k = 10;
while ($k < 5) { print "never\n" }
print "the plain while ran zero times\n";do-while body ran, i=10
the plain while ran zero timesSame condition, same starting value, and the two forms disagree about whether to run at all. $i < 5 was false before either loop started — the do body ran anyway, and the plain while did not.
do-until works the same way:
use strict;
use warnings;
my $i = 10;
do { print "do-until body ran, i=$i\n" } until ($i > 5);do-until body ran, i=10$i > 5 was already true at entry — which for until means "stop" — and the body still ran once.
| Form | Minimum iterations | Condition tested |
|---|---|---|
while (COND) { ... } | 0 | before each pass |
until (COND) { ... } | 0 | before each pass |
do { ... } while (COND); | 1 | after each pass |
do { ... } until (COND); | 1 | after each pass |
Note the semicolon after the condition. It is required, because — as §3 explains — this is a statement with a modifier attached rather than a block construct.
2. The Shape It Is Actually For
A retry is the honest use case: you cannot know whether to retry before trying.
use strict;
use warnings;
my @responses = (0, 0, 1); # succeeds on the third attempt
my $attempt = 0;
my $ok = 0;
do {
$attempt++;
$ok = $responses[$attempt - 1] // 0;
print " attempt $attempt: ", ($ok ? "ok" : "not yet"), "\n";
} while (!$ok && $attempt < 5);
printf "finished after %d attempt(s), ok=%d\n", $attempt, $ok; attempt 1: not yet
attempt 2: not yet
attempt 3: ok
finished after 3 attempt(s), ok=1Three things about that loop are worth copying:
- The first attempt is unconditional, which is what makes the form fit. A plain
while (!$ok && ...)would also work here because$okstarts false — but only because it was initialised to false on purpose, and that initialisation exists solely to make the pre-test loop behave like a post-test one. $attempt < 5is in the condition. Chapter 9.8 is the chapter on this, and it matters more here than in other loops: a retry whose condition depends on something external is the classic shape that never terminates.// 0supplies a default past the end of the list rather than lettingundefwarn — Chapter 8.5 §4's operator.
The same shape covers reading until a sentinel, prompting until the input is valid, and polling a file that may not exist yet.
3. It Is Not a Loop, and the Error Message Says So
use strict;
use warnings;
my $i = 0;
do {
$i++;
last if $i > 2;
} while ($i < 10);
print "i=$i\n";Can't "last" outside a loop block at dowhile-last.pl line 8.255use strict;
use warnings;
my $i = 0;
do {
$i++;
next if $i < 3;
} while ($i < 5);
print "i=$i\n";Can't "next" outside a loop block at dowhile-next.pl line 8.255Read the wording: "outside a loop block". Perl is not saying last is disallowed here as a special case — it is saying the enclosing construct is not a loop, so there is nothing for last to leave.
That is literally true. do { ... } is an expression that evaluates a block and yields its last value, and the while on the end is the statement modifier from Chapter 8.3. So do { } while ( ) is "evaluate this expression repeatedly while the condition holds" — which produces loop-shaped behaviour out of parts that are not a loop. Chapter 8.3 §7 measured that a bare block cannot take a modifier at all, and this is the reason do { } can: it is an expression, not a block statement.
4. A Bare Block Is a Loop
The quirk is worth knowing because it is the documented workaround.
use strict;
use warnings;
my $i = 0;
LOOP: {
$i++;
redo LOOP if $i < 3;
}
print "bare block with redo: i=$i\n";bare block with redo: i=3A bare block is a loop that executes exactly once, and the loop-control keywords all work inside it:
| Keyword in a bare block | Effect |
|---|---|
last | leave the block |
next | leave the block — measured i=1, since there is no next iteration |
redo | run the block again from the top |
So redo turns a bare block into a loop that repeats as many times as you like, with the test wherever you put it. That gives you do-while's test-at-the-bottom behaviour and working loop control.
It is not a common idiom and it will make some readers pause, so prefer while (1) with last for anything another person will maintain. Know this one so that redo LOOP in inherited code reads correctly, and for the case where last inside a do block has to be fixed quickly.
Chapter 9.5 covers redo properly, including why it is rare and what it does to the loop list.
5. Why This Matters in Engineering Work
The first-attempt-unconditional shape is real. Polling for a file a tool is about to write, retrying a flaky network mount, prompting until an answer parses — each must act before it can test. do-while expresses that directly.
But the restriction usually outweighs the convenience. The moment a retry needs to abandon early on a fatal error — which is most retries worth writing — you need last, and inside a do block that is a runtime death. Starting with while (1) avoids the rewrite.
And the failure mode is the dangerous kind. A last on an error path that never fires in testing compiles, passes perl -c, and runs correctly for months. The thing that triggers it is the error it was written to handle.
6. Common Mistakes
Using last or next inside a do { } while. Fatal at run time: Can't "last" outside a loop block.
Assuming that failure is caught at compile time. It is not, and perl -c passes. A guarded last ships.
Forgetting the semicolon after do { ... } while (COND);. It is a statement, not a block construct.
Reaching for do-while when the body is non-trivial. Use while (1) with an explicit last so loop control is available.
Expecting do { } until (COND) to skip the body when the condition is already true. It always runs once.
Writing a do-while retry with no attempt bound. It may never finish — Chapter 9.8 covers it.
Reading a bare block as not-a-loop. It is one, which is why redo works there.
7. Exercise
Exercise 1 — one pass minimum. With $i = 10, write do { ... } while ($i < 5); and the equivalent plain while. Record how many times each body ran.
Exercise 2 — the until form. With $i = 10, write do { ... } until ($i > 5);. Record whether the body ran and explain it in one sentence.
Exercise 3 — the retry. Build §2's retry over (0, 0, 1) and confirm it finishes on attempt 3. Then change the list to (0, 0, 0, 0, 0, 0) and confirm the attempt bound stops it at 5.
Exercise 4 — last inside. Add last if $i > 2; to a do-while body. Record the exact error message and the exit status. Then do the same with next.
Exercise 5 — the one that ships. Put a last inside a do-while behind a condition that is false, and confirm the script runs cleanly. Then run perl -c on it and record the result. Finally make the condition true and record the failure.
Exercise 6 — the real loop. Rewrite Exercise 5 as while (1) with an explicit last. Confirm it works in both the false and true cases.
Exercise 7 — the bare block. Write LOOP: { $i++; redo LOOP if $i < 3; } and confirm $i ends at 3. Then replace redo with next and record what happens.
Exercise 8 — which form. For each of these, say which loop form you would write and why: reading a file to EOF; prompting until the input parses; iterating a list of blocks; retrying a mount up to five times with an early exit on a permissions error.
8. Summary
do { ... } while (COND);runs the body once before testing, so the minimum is one iteration wherewhileanduntilhave a minimum of zero.do { ... } until (COND);is the same, with the condition reversed.- The trailing semicolon is required — it is a statement with a modifier, not a block construct.
- A
doblock is not a loop.lastandnextinside it are fatal:Can't "last" outside a loop block, exit 255. - That error is at run time, not compile time.
perl -cpasses, and alaston an untaken error path ships and fails later. - A bare block is a loop that runs once, so
last,nextandredoall work there —redo LOOPgives test-at-the-bottom with working loop control. - Prefer
while (1)with an explicitlastfor anything beyond a trivial retry. It trades the implicit first pass for loop control, which is usually worth it. - Bound every retry. A
do-whilewaiting on something external may never finish.
Chapter 9.5 covers the three loop-control keywords themselves — next, last and the rarely-seen redo — including what redo does to the loop's position.
Where this fits
Part of the Perl curriculum.
