Lvalue subs and context#

Two related topics that come up wherever a sub needs to behave differently depending on how it is being used: :lvalue subs (those usable on the left-hand side of an assignment) and wantarray-driven dispatch on the caller’s context.

Why context matters in subs#

Every Perl expression evaluates in a context — list, scalar (with sub-flavours: numeric, string, boolean), or void — that’s imposed by what surrounds it. A sub call inherits the context of its position:

my @r = my_sub();                # list context
my $r = my_sub();                # scalar context
my_sub();                        # void context
print my_sub();                  # list context (print's args are LIST)
my $n = () = my_sub();           # scalar of (a list assignment) — element count

For most subs, this is invisible — they return the same data either way and Perl’s automatic context conversion does the right thing. For some subs the work is genuinely different depending on context, and those subs benefit from explicitly inspecting wantarray.

wantarray: the three-way switch#

sub items {
    if (wantarray) {
        return (1, 2, 3);                  # list context
    }
    elsif (defined wantarray) {
        return 3;                          # scalar context
    }
    else {
        return;                            # void context — caller will
                                           # discard whatever we return
    }
}

The three return values of wantarray:

Return value

Caller’s context

Practical meaning

1 (true)

list

return all the things

0 (false)

scalar

return one thing (e.g. count)

undef

void

nobody cares; consider returning early

defined wantarray is the test for «scalar or list» (anything non-void).

The void-context shortcut#

When work is expensive and the caller threw the result away, return early:

sub expensive_query {
    return unless defined wantarray;       # nothing to compute
    my @rows = $db->select(...);           # actually do the work
    return wantarray ? @rows : scalar @rows;
}

expensive_query();                         # void: returns immediately
my @r = expensive_query();                 # list: full result
my $n = expensive_query();                 # scalar: count

This pattern is the single best reason to consult wantarray: turning a «always do the work» sub into a «do the work only if the caller wants it» sub.

When not to use wantarray#

  • For convenience overloading. A sub that returns a hashref in scalar context and a hash in list context is a maintenance hazard — readers can’t tell from the call site which they’re getting. Pick one.

  • For subs that are obviously list-y or scalar-y. A sub named count_users should return a number; readers do not expect it to behave differently in list context.

:lvalue subs#

An :lvalue sub is one whose return value is itself assignable. The body must end in an expression that has a memory location — typically a my lexical or an aggregate element:

sub editable :lvalue {
    my $self = shift;
    $self->{counter};                      # last expression is an lvalue
}

my $obj = { counter => 0 };
bless $obj, 'Counter';

$obj->editable = 42;                       # writes through to $obj->{counter}

The body’s last expression is what the assignment writes to. If that expression is not an lvalue (return $x + 1, return scalar @list), the assignment is illegal and you’ll get a clear error.

:lvalue is rarely the right tool. The only places it pays for itself:

  • Tied variables and tied-like wrappers, where the sub is mediating access to a real lvalue underneath.

  • DSLs that genuinely want assignment-syntax for setting (eg. $config->host = 'localhost';).

For «set a property on an object», a plain setter is clearer:

sub set_counter {
    my ($self, $value) = @_;
    $self->{counter} = $value;
    return $self;                           # method chaining
}

$obj->set_counter(42);                     # explicit, no surprises

:lvalue and the body’s structure#

Because the body’s last expression is special, the rules are restrictive:

sub good :lvalue { $self->{x} }            # OK — last expr is lvalue

sub also_good :lvalue {
    my @scratch = ...;                     # work
    $self->{x};                            # last expr is lvalue
}

sub bad :lvalue {
    return $self->{x};                     # NOT OK — explicit return
                                           # disables :lvalue
}

sub also_bad :lvalue {
    if ($cond) { $self->{x} }
    else       { $self->{y} }              # an `if` block is not an lvalue
}

The «no explicit return» rule is the same one that makes constant-fold prototypes work; both rely on the body being a single value-producing expression at the end.

For the modern alternative — a setter that returns $self for chaining — see the Object-Oriented Programming guide.

Combining :lvalue with context#

An :lvalue sub cannot easily branch on wantarray, because the lvalue version of the call doesn’t quite fit the «returns a value» model. If you want both behaviours from one sub, accept that you are in DSL territory and document the contract carefully — or split into two subs.

Context-affecting operators#

A few operators force a specific context on their argument; the sub will see that context regardless of where the surrounding expression is going:

my @r = scalar my_sub();         # scalar() forces scalar context on my_sub
my $n = () = my_sub();           # the inner () = ... is list context;
                                 # outer assignment to $n is scalar of that

The () = LIST idiom is the standard way to force list context on a sub call and then read the count. See assignment for the operator-level discussion.

See also#

  • wantarray — the keyword.

  • Return values — the rest of the return-value picture (without the lvalue twist).

  • Attributes:lvalue is one of several built-in attributes.

  • Assignment operator — what the caller’s = actually does on either side of an :lvalue call.

  • scalar — explicit context coercion.