Skip to content
VLSI Mentor

Perl Part I · Module 10 · Subroutines and Arguments

wantarray: Returning One Value or a List

Chapter 10.6 showed that the caller decides what a return means. wantarray lets the subroutine find out what was decided, and return something shaped to match — a list of failing blocks when the caller wants a list, the number of them when the caller wants one value. Used in the one place it genuinely fits, that is a neat interface. Used more widely it produces a subroutine whose behaviour cannot be described without describing its caller, which is the opposite of what a reusable subroutine is for. This is the only chapter in the module the curriculum marks Medium rather than High or Critical, and the measurement in section four is why.

Advanced13 min readPerlPart ISubroutines

Part I · Module 10 · Chapter 10.7 · wantarray: Returning One Value or a List

1. Three Answers, Not Two

Azvya Education Pvt. Ltd.VLSI Mentor
wantarray-three.pl
use strict;
use warnings;

sub probe {
    my $w = wantarray;
    printf "  wantarray = %s\n",
        (defined $w ? ($w ? "1 (list)" : "empty string (scalar)") : "undef (void)");
    return 1;
}

print "called in list   :\n"; my @a = probe();
print "called in scalar :\n"; my $b = probe();
print "called in void   :\n"; probe();
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
called in list   :
  wantarray = 1 (list)
called in scalar :
  wantarray = empty string (scalar)
called in void   :
  wantarray = undef (void)

wantarray is not a boolean. It has three possible values, and the name — which suggests a yes-or-no question about arrays — is actively misleading:

Caller's contextwantarray returnsTest with
list — my @x = f(), my ($x) = f()1 (true)if (wantarray)
scalar — my $x = f(), if (f())"" (false, defined)if (defined wantarray && !wantarray)
void — f(); on its ownundefif (!defined wantarray)

The ordering of tests therefore matters, and it is the same ordering Chapter 6.4 established for hash values: truth first, then definedness.

Azvya Education Pvt. Ltd.VLSI Mentor
The correct shape
sub context_of {
    return "list"   if wantarray;              # 1
    return "scalar" if defined wantarray;      # "" — defined but false
    return;                                    # undef — void
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — wantarray.pl
called in list context   : list
called in scalar context : scalar

Note what the void branch can do: nothing useful. There is nowhere for a void-context return value to go, so detecting void context is only worth it to skip work — a subroutine that would build an expensive list can return early if nobody is going to look at it. That is a real optimisation and a rare one.

2. The One Good Use

Azvya Education Pvt. Ltd.VLSI Mentor
wantarray-useful.pl
use strict;
use warnings;

sub failing_blocks {
    my @bad = ("alu", "regf");
    return wantarray ? @bad : scalar(@bad);
}

my @list  = failing_blocks();
my $count = failing_blocks();

printf "list context   : %s\n", join(" ", @list);
printf "scalar context : %d\n", $count;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
list context   : alu regf
scalar context : 2

Both call sites read naturally. my @list = failing_blocks() gives the blocks; my $count = failing_blocks() gives how many. One subroutine, two sensible interfaces, no second function name.

What makes this case work is that the two answers are about the same question. "Which blocks failed" and "how many blocked failed" are the list and the size of the same thing, so a reader who guesses either way guesses right.

This is also exactly what Perl's own builtins do. grep in list context gives the matches and in scalar context gives the count — Chapter 5.10 §5 measured it — and reverse gives a reversed list or a reversed string. So the pattern is idiomatic when it mirrors that: the scalar answer is a summary of the list answer.

Note that scalar(@bad) is written explicitly rather than relying on return @bad collapsing. Chapter 10.6 §4 argued for that, and here it is doubly worth it: the whole point of the subroutine is that it is deliberate about context, so leaving one branch to an implicit rule undercuts it.

3. Writing It Correctly

Three details that are easy to get wrong.

Azvya Education Pvt. Ltd.VLSI Mentor
Do not call it twice
return wantarray ? @bad : scalar(@bad);          # fine — one call

wantarray is cheap, so calling it twice is not a performance problem — but assigning it once makes the three-state logic readable:

Azvya Education Pvt. Ltd.VLSI Mentor
Clearer when all three branches are needed
sub f {
    my $want = wantarray;
    return            unless defined $want;   # void: do nothing
    return @list      if $want;               # list
    return scalar(@list);                     # scalar
}

wantarray reports the context of the current subroutine call, not of any enclosing expression. So this does not work:

Azvya Education Pvt. Ltd.VLSI Mentor
A helper cannot see its caller's caller
sub outer { return inner() }        # inner sees outer's RETURN context
sub inner { return wantarray ? "list" : "scalar" }

In that arrangement inner's wantarray reflects the context of return inner() inside outer, which Perl propagates from outer's own caller — so it happens to work here. But insert anything between, and it stops:

Azvya Education Pvt. Ltd.VLSI Mentor
Now inner is in list context regardless
sub outer { my @tmp = inner(); return @tmp }

Measured, with both forms of outer called in scalar context:

Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
my $x = direct()  -> SCALAR
my $x = viatemp() -> 1

The second answer is worth a second look: it is not "LIST". my @tmp = inner() forced list context, so inner returned the string "LIST" into a one-element array — and then return @tmp in the caller's scalar context gave that array's count. Chapter 10.6 §1's rule, stacked on top of the wantarray branch.

That is the first sign of the real problem: a wantarray-based subroutine's behaviour depends on code that is not in it, so adding a temporary variable in a caller changed the answer from a string to the number 1.

4. Why This Chapter Is Marked Medium

5. Where You Will Actually Meet It

Three places wantarray turns up in code you did not write:

Accessor-style helpers that return a list or its size:

Azvya Education Pvt. Ltd.VLSI Mentor
Common, and the good case
sub ports { return wantarray ? @{ $self->{ports} } : scalar @{ $self->{ports} } }

Error-reporting subroutines that return details or a flag:

Azvya Education Pvt. Ltd.VLSI Mentor
Common, and the bad case
sub validate {
    my @errors = check_all();
    return wantarray ? @errors : !@errors;     # list of errors, or a boolean OK
}

That second one inverts the truth value between contexts. Measured, both error counts in both contexts:

Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
  errors=0 : list gives 0 item(s), scalar gives TRUE
  errors=2 : list gives 2 item(s), scalar gives false

With no errors the list is false and the scalar is true. So if (validate()) means "valid" and my @e = validate(); if (@e) means "invalid" — both are reasonable readings of the same subroutine name, and they are exact opposites. If you meet one, read it twice.

Context-preserving wrappers, which are the one place wantarray is close to necessary:

Azvya Education Pvt. Ltd.VLSI Mentor
A wrapper that must not change its wrapped call's context
sub timed {
    my ($fn) = @_;
    my $start = time;
    if (wantarray) {
        my @r = $fn->();
        warn "took " . (time - $start) . "s\n";
        return @r;
    }
    my $r = $fn->();
    warn "took " . (time - $start) . "s\n";
    return $r;
}

Without the wantarray branch, a wrapper forces one context on everything it wraps — so a wrapped array-returning function would always hand back a count. This is the pattern that justifies wantarray existing, and it is also why wrappers are more verbose in Perl than the thing they wrap.

6. Why This Matters in Engineering Work

You will read it before you write it. Accessor helpers in inherited flow libraries use it constantly, and knowing it is three-state rather than boolean is what lets you read the defined test correctly.

The count-instead-of-a-row failure is a real shape. my $row = get_rows(); is a natural thing to write, gives 3, and the script then treats a number as a row. The subroutine is not at fault, which is why the investigation goes to the wrong place.

And two names beat one context-sensitive name for anything other scripts call. failing_blocks() and failing_count() are self-describing at the call site and need no test matrix over contexts.

7. Common Mistakes

Treating wantarray as a boolean. It has three values; void context returns undef.

Testing if (wantarray) for scalar context. That branch is false for both scalar and void. Use defined wantarray.

A scalar branch returning a different kind of thing from the list branch. The caller cannot guess; use two subroutines.

Returning a list of errors in list context and a success flag in scalar context. The truth value inverts between the two.

Testing only one context. The untested path is the one callers hit by accident.

Assuming a wrapper preserves context. It does not unless it branches on wantarray.

Relying on implicit collapse in the scalar branch. Write scalar(@list) explicitly.

8. Exercise

Exercise 1 — three states. Write a subroutine that prints what wantarray returned, then call it in list, scalar and void context. Record all three and say which one defined distinguishes.

Exercise 2 — the correct test order. Write context_of returning "list", "scalar" or nothing. Call it in all three contexts and confirm each. Then swap the two tests and record what scalar context now reports.

Exercise 3 — the good case. Write failing_blocks from §2 and call it both ways. Confirm the list and the count.

Exercise 4 — the trap. Write rows from §4 and call it as my ($first) = rows() and my $maybe = rows(). Record both. Confirm STDERR is empty and the exit status is 0.

Exercise 5 — the test that misses it. Write a test for rows that only uses list context and confirm it passes. Then break the scalar branch — return "oops" instead of the count — and confirm the test still passes.

Exercise 6 — two names. Rewrite rows as two subroutines with fixed contracts. Rewrite both call sites and say which version you would put in a shared library.

Exercise 7 — the inverting validator. Write §5's validate returning errors or !@errors. Call it with no errors and with two, in both contexts, and build the full four-cell table. Point at the two cells that read as opposites.

Exercise 8 — the wrapper. Write a wrapper that calls a code reference and reports how long it took, without the wantarray branch. Call it wrapping an array-returning subroutine in list context and record what you get. Then add the branch and record it again.

9. Summary

  • wantarray has three values, not two: 1 in list context, the empty string in scalar context, and undef in void context.
  • So test truth first, then definedness — if (wantarray) then if (defined wantarray). if (wantarray) alone is false for both scalar and void.
  • Void context can only be used to skip work, since there is nowhere for a return value to go.
  • The good use is when the scalar answer summarises the list answer — the blocks and their count. That mirrors grep and reverse, and reads correctly either way.
  • Write scalar(@list) explicitly in the scalar branch rather than relying on implicit collapse.
  • The cost is that the subroutine's contract moves to the call site. Measured: my $maybe = rows() returned 3 where the caller meant one row — correctly, by design, with no warning.
  • Tests must cover both contexts, because the untested path is the one callers reach by accident.
  • A wrapper does not preserve its wrapped call's context unless it branches on wantarray — the one case where it is close to necessary.
  • Two names beat one context-sensitive name for anything shared.

Chapter 10.8 covers the other half of returning: handing back a container rather than a list, so two things do not arrive joined — and what a bare return gives in each context.

Where this fits

Part of the Perl curriculum.