Skip to content
VLSI Mentor

Perl Part I · Module 9 · Loops

Labels and Nested Loops

Chapter 9.5 measured that a bare last leaves only the loop it is written in, so a search that finds its answer in an inner loop carries on with the outer one regardless. The usual first fix is a flag variable — set it in the inner loop, test it at the top of the outer one — and it works, but it spreads one decision across three places and is easy to get subtly wrong. Perl's alternative is to give the loop a name, so last and next can say which loop they mean. It is two extra words and it removes the flag entirely. The one cost is that a label Perl cannot find is not a syntax error, so a misspelled one on a branch your tests never take will pass every check you have and fail in production.

Intermediate13 min readPerlPart IControl FlowLoops

Part I · Module 9 · Chapter 9.6 · Labels and Nested Loops

1. last LABEL Exits the Loop You Name

Azvya Education Pvt. Ltd.VLSI Mentor
label-last.pl
use strict;
use warnings;

OUTER: for my $i (1 .. 3) {
    for my $j (1 .. 3) {
        if ($i == 2 && $j == 2) {
            print "(bailing at i=$i j=$j) ";
            last OUTER;
        }
        print "$i$j ";
    }
}
print "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
11 12 13 21 (bailing at i=2 j=2)

11 12 13 is the whole first outer pass. 21 is the first item of the second. Then last OUTER ended both loops — there is no 22, no 23, and no i=3 at all.

The syntax is two parts:

  • LABEL: before the loop — a name followed by a colon.
  • last LABEL or next LABEL inside, naming which loop to act on.

A bare last would have ended only the inner loop, which Chapter 9.5 §2 measured as 11 21 31.

2. next LABEL Abandons the Rest of the Outer Pass

Azvya Education Pvt. Ltd.VLSI Mentor
label-next.pl
use strict;
use warnings;

OUTER: for my $i (1 .. 3) {
    for my $j (1 .. 3) {
        next OUTER if $j == 2;
        print "$i$j ";
    }
}
print "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
11 21 31

Each outer pass printed exactly one inner item and then jumped to the next outer item. next OUTER does not merely leave the inner loop — it advances the outer loop, skipping whatever remained of its body.

That is the distinction worth holding:

From inside the inner loopEffect
lastleave the inner loop; the outer pass continues
last OUTERleave both loops entirely
nextnext inner item
next OUTERnext outer item, abandoning the rest of this outer pass

Labels work on every loop form, and on a bare block:

Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — a labelled while and a labelled bare block
while label: i=3
block label: k=3
Azvya Education Pvt. Ltd.VLSI Mentor
Both forms
W: while (1) { $i++; last W if $i > 2 }
B: { $k++; redo B if $k < 3; }

The bare-block case is Chapter 9.4 §4's workaround, and the label is what makes redo there unambiguous.

3. Against the Flag Variable

The flag version is what the label replaces, and it is worth being accurate about why.

Azvya Education Pvt. Ltd.VLSI Mentor
flag-version.pl
use strict;
use warnings;

my $done = 0;

for my $i (1 .. 3) {
    last if $done;
    for my $j (1 .. 3) {
        if ($i == 2 && $j == 2) {
            print "(bailing at i=$i j=$j) ";
            $done = 1;
            last;
        }
        print "$i$j ";
    }
}
print "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
11 12 13 21 (bailing at i=2 j=2)

Byte-identical to §1's output. The flag version is not wrong, and it is not slower in any way you could measure. Both exit 0, both produce the same five tokens.

So the case for labels is about the code, not the behaviour:

LabelledFlag
places the decision appears13 — declare, set, test
extra variablenoneone, in the outer scope
what happens if you forget a piecenothing to forgetthe loop silently continues
reads as"stop everything""set a thing, then check a thing"

The failure mode of the flag version is the point. Three things have to be right: the declaration, the $done = 1, and the last if $done at the top of the outer loop. Omit the last of those and the inner loop still stops, the outer loop keeps going, and the only symptom is extra work being done — which on a two-level loop over a few items is invisible, and on a scan over a thousand blocks is a performance mystery rather than a visible bug.

There is also a placement subtlety, and it is worth measuring rather than asserting, because the obvious version of the claim is wrong. Putting a header print in the outer body of all three variants:

Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — gap.pl
labelled:
  -- corner 1 --
  -- corner 2 --
flag, test ABOVE the side effect:
  -- corner 1 --
  -- corner 2 --
flag, test BELOW the side effect:
  -- corner 1 --
  -- corner 2 --
  -- corner 3 --

The first two agree; only the third does an extra pass. So the flag is not inherently leaky — it leaks when the test is placed after something with a side effect, which is an easy thing to do when the outer body opens with setup code and the flag check gets added later, at the point where it seemed relevant.

A label cannot be placed wrongly in this way: last OUTER acts the moment it executes, from wherever it is, so there is no ordering to get right.

Use a flag when you genuinely need to know afterwards whether the loop bailed. That is a real case, and then the flag is carrying information rather than control:

Azvya Education Pvt. Ltd.VLSI Mentor
A flag that earns its place
my $found;
CORNER: for my $corner (@corners) {
    for my $blk (@blocks) {
        if (is_critical($corner, $blk)) { $found = "$corner/$blk"; last CORNER }
    }
}
print defined $found ? "first critical: $found\n" : "all clean\n";

Label for the control flow, variable for the result. That is the combination to write, and it is what §5 does.

4. A Misspelled Label Is Not a Syntax Error

Azvya Education Pvt. Ltd.VLSI Mentor
label-typo.pl
use strict;
use warnings;

OUTER: for my $i (1 .. 2) {
    last TYPO;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDERR
Label not found for "last TYPO" at label-typo.pl line 5.
Azvya Education Pvt. Ltd.VLSI Mentor
Measured exit status
255

That looks like a compile failure. It is not.

Azvya Education Pvt. Ltd.VLSI Mentor
perl -c label-typo.pl
label-typo.pl syntax OK
Azvya Education Pvt. Ltd.VLSI Mentor
Measured exit status of perl -c
0

perl -c passes. Label not found is raised when control reaches the statement, so a last WRONGNAME on a branch your tests never take will clear every static check you have — perl -c, use strict, use warnings — and die the first time that branch is taken.

5. The Convention, and What Is Actually Required

Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — a lowercase label, run under -w
lowercase ok
Azvya Education Pvt. Ltd.VLSI Mentor
Measured exit status
0
Azvya Education Pvt. Ltd.VLSI Mentor
Measured — this is legal and silent
outer: for my $i (1 .. 2) { last outer }

Lowercase labels work and produce no warning, even with warnings enabled. So the ALL-CAPS convention is exactly that — a convention, with no enforcement behind it.

It is worth following anyway, for one practical reason: a label is a bare word in a position where Perl otherwise expects keywords and variables, and last outer reads like it might be a function call or a variable while last OUTER cannot be mistaken for either. The convention makes the construct self-announcing.

Three habits that are worth more than the capitalisation:

  • Name the loop after what it iterates, not after its position. CORNER: and BLOCK: beat OUTER: and INNER:, because last CORNER says what is being abandoned. OUTER: is fine for two tight loops in one screenful and gets confusing the moment a third level appears.
  • Label a loop only when something names it. An unused label is noise, and it invites a reader to look for the last that is not there.
  • One label per loop level, and do not reuse a name in the same file for two different loops.

Here is the shape in a realistic form:

Azvya Education Pvt. Ltd.VLSI Mentor
label-search.pl
use strict;
use warnings;

my %report = (
    ss => { alu => -0.12, fifo =>  0.34 },
    ff => { alu =>  0.05, regf => -0.40 },
);

my ($found_corner, $found_block);

CORNER: for my $corner (sort keys %report) {
    for my $blk (sort keys %{ $report{$corner} }) {
        if ($report{$corner}{$blk} < -0.3) {
            ($found_corner, $found_block) = ($corner, $blk);
            last CORNER;
        }
    }
}

printf "first critical: %s / %s\n",
    ($found_corner // "none"), ($found_block // "none");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
first critical: ff / regf

Both levels are sorted, so "first" means something reproducible — Chapter 6.5's rule, and without it this program's answer would vary between runs for a report with two critical blocks. The label carries the control flow; the two variables carry the result; and // supplies the not-found case from Chapter 8.5.

6. Why This Matters in Engineering Work

Two-level searches are everywhere. Corner by block, file by line, instance by pin. Any of them that stops on the first match needs to stop both loops, and a bare last does not.

The flag version's failure is silent and gradual. Forgetting the outer test means extra work rather than a wrong answer, so it shows up as a script that got slower, not as a bug — and nobody bisects a performance change looking for a missing last if $done.

And the runtime-only label error belongs on the list of things perl -c cannot tell you. Alongside last in a do block, it means a clean perl -c is evidence about syntax and nothing else. Both failures live on error paths by nature, which is exactly where they survive testing.

7. Common Mistakes

A bare last where you meant to exit both loops. It exits the innermost only.

Reading next OUTER as "leave the inner loop". It advances the outer loop, abandoning the rest of that pass.

A misspelled label. Label not found, exit 255, and perl -c reports syntax OK.

A flag variable without the outer test. The inner loop stops, the outer one carries on, and the symptom is wasted work.

A flag test placed below setup code in the outer body. That setup runs one extra time — measured, 3 passes against 2.

Labelling a loop nothing refers to. Noise that makes a reader hunt for a last that does not exist.

OUTER:/INNER: at three levels deep. Name them after what they iterate.

Assuming ALL-CAPS is required. It is convention only — lowercase works and does not warn.

8. Exercise

Exercise 1 — last LABEL. Reproduce §1 and confirm the output. Then remove OUTER from the last and record how the output changes.

Exercise 2 — next LABEL. Reproduce §2 and confirm 11 21 31. Then change it to a bare next and explain the difference in one sentence.

Exercise 3 — the flag version. Write §3's flag loop and diff its output against §1's. Confirm they match.

Exercise 4 — the flag's failure. Delete the last if $done; line from Exercise 3. Record the output and say in one sentence what changed and what did not.

Exercise 5 — where the gap actually is. Add print " -- corner $i --\n"; to the outer body in three variants: the labelled loop; the flag loop with last if $done; above the print; and the flag loop with it below the print. Record how many times the header prints in each, and state in one sentence which placement leaks and why a label cannot.

Exercise 6 — the typo. Write last TYPO inside a labelled loop. Record the runtime error and exit status, then run perl -c on the same file and record its output and exit status.

Exercise 7 — the version that ships. Put last TYPO behind if ($fatal) with $fatal = 0. Confirm the script runs clean and perl -c passes. Then set $fatal = 1 and record the failure.

Exercise 8 — the realistic search. Build §5's two-level %report and find the first block worse than -0.3 with last CORNER. Then remove both sort calls, run it five times, and report whether the answer was stable.

9. Summary

  • LABEL: before a loop names it; last LABEL and next LABEL act on that loop.
  • last OUTER leaves both loops — measured 11 12 13 21 then nothing.
  • next OUTER advances the outer loop, abandoning the rest of its body — measured 11 21 31.
  • Labels work on for, foreach, while, until and a bare block.
  • The flag-variable version produces byte-identical output. The case for labels is one place instead of three, no extra variable, and nothing to forget.
  • A flag leaks only when its test sits below a side effect — measured 3 outer passes against 2. Placed at the very top it is equivalent to the label; a label cannot be misplaced this way at all.
  • Keep a flag only when you need the result afterwards, and use the label for the control flow.
  • A misspelled label is a runtime error: Label not found, exit 255 — and perl -c reports syntax OK.
  • That is the second runtime-only loop failure in this module, after last in a do block. Both live on error paths, where tests do not reach them.
  • ALL-CAPS is convention, not a rule — lowercase works and does not warn. Name labels after what they iterate.

Chapter 9.7 covers the variable Perl uses when you do not name one, which several of these constructs set — and the one of them that does not clean up after itself.

Where this fits

Part of the Perl curriculum.