Scoping

local#

Save the current value of a package variable and restore it when the enclosing scope exits.

local is the dynamic-scope operator. It does not declare a new variable — it reaches into an existing package (global) variable, stashes the current value on a hidden save stack, and arranges for the original value to be put back when control leaves the current block, eval, or do FILE. Called subroutines see the temporary value. For most “I want a variable local to this block” needs, the right tool is my; reach for local specifically when the variable must be a package global — the punctuation variables, filehandles, or entries in the symbol table — and you want that global to have a different value for the duration of a block and every subroutine it calls.

Synopsis#

local EXPR
local $var
local $var = EXPR
local (@list) = LIST
local $hash{key}
local $array[i]
local *GLOB

What you get back#

The value of the localized expression (the new value, after any assignment in the same statement). The save-stack bookkeeping is a side effect; the return value matters only when local appears inside a larger expression:

our $x = 2;
foo($x, local $x = $x + 1, $x);   # foo() receives (2, 3, 3)

Dynamic scope, not lexical scope#

local and my are different species of scoping:

  • my creates a new, lexically scoped variable. Only code textually inside the enclosing block can see it. Called subroutines cannot.

  • local does not create anything. It temporarily overrides the value of an existing package variable. Every piece of code — this block and every subroutine it calls, directly or transitively — sees the new value until the scope ends.

That is why local is the right tool for punctuation variables like $/, $\, $,, $_, and $@: those are package globals that the Perl runtime (and anything called from your code) reads at well-known names. A my shadowing would not affect them.

sub slurp {
    my ($path) = @_;
    open my $fh, "<", $path or die "open $path: $!";
    local $/;                     # undef $/ for this sub only
    return <$fh>;                 # read to EOF in one go
}

The local $/ line above is the single most common local idiom in real code: temporarily undefining the input record separator so that readline (<$fh>) returns the whole file. When slurp returns, $/ is restored to whatever it was before — even if the caller had also changed it.

What you can and cannot localize#

You can localize:

  • Package scalars, arrays, hasheslocal $Pkg::var, local @Pkg::arr, local %Pkg::hash. Bareword local $var targets the variable in the current package.

  • Already-declared our variablesour creates a lexical alias to a package variable, so local on it localizes the underlying global.

  • Single elements and slices of package arrays/hasheslocal $hash{key}, local $array[3], local @hash{qw(a b)}, local @array[0..2].

  • Globslocal *name creates a fresh symbol-table entry so that $name, @name, %name, &name, and the name filehandle are all dynamically reset together.

  • Conditional lvalueslocal ($cond ? $v1 : $v2) when the chosen branch is itself localizable.

You cannot localize:

  • A my variable. my variables are lexicals living in a pad, not in the symbol table; they have no package name for local to save under. Trying it is a compile-time error: Can't localize lexical variable $x.

  • A state variable. Same reason.

  • Read-only magic globals. local $1 = 2 fails with Modification of a read-only value attempted — the capture variables cannot be assigned to. (The one exception is $_: local $_ has, since 5.14, explicitly stripped magic so it can be reused safely in a subroutine.)

Localizing composite-type elements#

local $hash{key} and local $array[i] save the named slot, not the value that happens to be there. When the scope ends, the original value is restored to that same slot — even if the element was deleted or the array was shortened in the meantime. A deleted hash key springs back; a popped array element reappears, padding the array with undef if necessary.

our %hash = (a => "is");
{
    local $hash{a} = "drill";
    delete $hash{a};              # gone for now...
}
print $hash{a}, "\n";             # ...but restored to "is"

Examples#

Basic: give a global a temporary value for one block. Subroutines called from inside the block see the new value.

our $verbose = 0;
sub log_msg { print "[v=$verbose] $_[0]\n" }

{
    local $verbose = 1;
    log_msg("inside");            # prints "[v=1] inside"
}
log_msg("outside");               # prints "[v=0] outside"

Slurp-a-file idiom with $/:

my $content = do {
    open my $fh, "<", $path or die "open $path: $!";
    local $/;                     # list context for <> now reads all
    <$fh>;
};

Temporarily trap $@ so an inner eval does not clobber the caller’s pending error:

sub try_cleanup {
    local $@;                     # caller's $@ preserved
    eval { risky_cleanup() };
    # even if eval failed, caller's $@ is untouched
}

Python-style print defaults, scoped to one block:

{
    local $, = " ";
    local $\ = "\n";
    print 1, 2, 3;                # "1 2 3\n"
}
print 1, 2, 3;                    # "123" — defaults restored

List form needs parentheses. The parentheses also give the right-hand side list context — exactly like my:

our (@wid, %get);
local (@wid, %get) = (@defaults, %overrides);

Localize a single hash element — useful for threading overrides through a call without touching unrelated keys:

our %config = (timeout => 30, retries => 3);
{
    local $config{timeout} = 5;
    do_probe();                   # sees timeout => 5
}
# %config back to (timeout => 30, retries => 3)

Glob localization — whole symbol-table entry for LOG is replaced:

{
    local *LOG;
    open LOG, ">", "/tmp/trace.$$" or die $!;
    run_traced();                 # sees our private LOG filehandle
}
# original LOG (if any) is back

Edge cases#

  • Runtime operator, not a declaration. local executes each time control reaches it. Inside a loop it saves and restores on every iteration — measurable overhead. Lift it out of the loop when the intent is “for the whole loop”:

    { local $/ = "\n\n";            # once, not per iteration
        while (<$fh>) { ... }
    }
    
  • Assignment is evaluated before the save. The right-hand side runs in the enclosing dynamic scope, so local $x = $x + 1 reads the old $x, then saves, then assigns the new value.

  • Scope boundary is dynamic, not lexical. The save is undone when the current block exits at runtime, not when the source block ends textually. Exiting via return, die, last, next, or a non-local goto still triggers the restore.

  • local on a my is a compile-time error. The parser rejects my $x; local $x; with Can't localize lexical variable $x. Use our (or a fully qualified name) if you genuinely need dynamic scoping on that name.

  • Tied arrays and hashes do not currently behave the way the POD describes — localizing the whole aggregate is buggy upstream. Localizing individual elements of a tied aggregate is safe. This caveat is inherited from upstream Perl.

  • Read-only magicals cannot be assigned. local $1 = "x" dies at runtime. You can local $_ (magic is stripped since 5.14) but not local $1, local $&, etc.

  • Negative array indices in local $array[-1] are documented as “particularly surprising” upstream; treat the behaviour as unstable and avoid.

  • delete local extends local to the removal of an element: delete local $hash{key} deletes the entry for the current block and restores it on exit. It returns the value that was there before localization.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • my — lexical (compile-time, block-scoped) declaration; the right tool when you want a truly private variable

  • our — lexical alias to a package variable; common partner for local when the global is in another package

  • state — persistent lexical; cannot be localized

  • delete — combined with local (delete local $h{k}) to scope an element deletion to the current block

  • eval — common local target: local $@ around an inner eval prevents it leaking errors to the caller

  • $/ — input record separator; canonical local target for slurp-mode file reads

  • $_ — default scalar; local $_ strips magic (since 5.14) so a subroutine can reuse it safely

  • $@ — last eval error; local $@ shields the caller from an inner eval’s error