ProgrammingBackend Developer

How do lexical and package variables work in Perl: what are the differences between my, our, and state, when to use them properly, and what errors are encountered in practice?

Pass interviews with Hintsage AI assistant

Answer

In Perl, there are three main types of variable declarations: my, our, and state.

  • my creates a lexical variable with a scope limited to the block of code (lexical scoping).
  • our creates a package variable that is globally accessible within the package but only visible in the lexical block. It is useful for organizing namespaces between modules.
  • state was introduced in Perl 5.10 and creates a lexical variable that retains its value between subroutine calls (essentially, a static variable).
sub example_state { state $counter = 0; $counter++; return $counter; } for (1..3) { print example_state(), " "; # Will output 1, then 2, then 3 }

Where to use them properly:

  • my — almost always the preferred choice for variables unless there is a reason for global access.
  • our — for exporting variables between modules when shared access is required.
  • state — when a variable should "remember" its value between function calls (e.g., a call counter).

Trick Question

Can a variable declared with our be captured in a closure just like my?

Typically, the answer is "yes, it's convenient," but that's not the case. An our variable is global to the package, and closure essentially does not bind its value at the time of closure creation but accesses it through a global name. Therefore, its "value" can change from outside the closure and affect all closures!

our $x = 10; my $closure = sub { return $x; }; $x = 42; print $closure->(); # Will return 42, not 10

Examples of Real Errors Due to Lack of Awareness on the Topic


Story

In a large script for parsing logs, variables were declared using our for "convenient access" from different functions of the module. As a result, changing these variables in one function led to unexpected behavior in another, breaking the parallel log processing.


Story

Using my for variables whose value needed to be retained between function calls (like a counter within a recursive traversal) resulted in the value being reset on each call, leading to incorrect traversal logic. Switching to state fixed the issue.


Story

Incorrect use of our in an imported module (exporting variables not through Exporter) caused a name collision when importing two different modules that used the same variable name. Consequently, data "jumped" from one context to another.