Perl adds finally to experimental exception handling
Perl 5.35.8 adds a cleanup block to try and catch. Its treatment of return and loop control explains why this belongs in the language grammar.
Cleanup after an exception is only part of the problem that finally has to solve. A function can leave a protected operation through an ordinary return, and a loop can leave it through next or last. Putting another statement after the exception handler does not cover those paths. Perl 5.35.8 extends its experimental exception syntax with an optional finally block that runs when the construct finishes, including when control transfers elsewhere. The release notes shipped with 5.35.8 describe the addition, and the release's history file records its January 20 release.
The interesting part is how this fits Perl's existing control flow. The syntax documentation treats try and catch as language blocks with the expected behavior for a containing function or loop. Cleanup must preserve that behavior without quietly replacing a return value. That makes the restrictions on finally as consequential as its ability to execute after an exception.
A handler and a cleanup block have different jobs
The versioned syntax documentation defines catch as the block that handles an exception thrown by try. Its parenthesized variable declaration introduces a lexical variable containing the exception value. The declaration does not need my. If the protected block completes normally, the handler does not run.
finally runs after the rest of that construct finishes. It therefore supplies a place for an action whose necessity does not depend on whether the operation succeeded. A handler can report a failure while the final block performs cleanup common to both outcomes. Combining those jobs in the handler would leave the successful path without that cleanup.
Here is a small illustration using only the documented syntax. The warning remains enabled deliberately because the feature is experimental in this release.
use strict;
use warnings;
use feature 'try';
sub attempt {
my ($fail) = @_;
try {
die "operation failed\n" if $fail;
print "operation succeeded\n";
}
catch ($error) {
warn $error;
}
finally {
print "operation finished\n";
}
}
attempt(0);
attempt(1);
The first call prints the success message and the final message. Calling it with a true argument enters the handler and also executes the final block. Messages make execution visible in this example. A real cleanup action belongs there only when it is appropriate after either outcome. The language cannot decide whether an application needs to discard partial work or preserve it for diagnosis.
I still value Perl for compact text processing, where the relationship between an operation and its surrounding control flow should remain easy to inspect. Explicit cleanup syntax appeals to me for the same reason. It puts the action beside the protected code without requiring the reader to reconstruct every exit path elsewhere in the function.
Return still belongs to the containing function
Perl's syntax manual makes an explicit distinction between try and eval. A return inside try returns from the containing function. A return inside an eval block only returns from that block. This is a semantic difference that matters when extracting existing statements into an exception handler.
Adding finally preserves the function return while arranging for the final block to run first. The following example illustrates that relationship without depending on an external resource or library.
use strict;
use warnings;
use feature 'try';
sub answer {
try {
return 42;
}
catch ($error) {
die $error;
}
finally {
print "leaving answer\n";
}
}
print answer(), "\n";
The final block's expression value does not become the function's result. The documentation says that value is ignored, including when the construct appears last in a function. Consequently, an incidental value produced by cleanup does not change the successful operation's return value.
The same documentation permits goto and loop controls within try and catch. It describes the final block as equivalent to a defer block for the situations in which it runs. That covers normal completion, an exception, and transfer through a return or loop control. A statement following the whole construct cannot provide the same coverage because some of those transfers leave its surrounding sequence of statements.
Cleanup cannot redirect execution
The final block has a narrower role than the blocks before it. It cannot use return, goto, or loop control expressions. The restriction prevents cleanup from acting as another ordinary branch that redirects execution. It also makes the source easier to read. A reader can inspect the protected operation's return without having to search the final block for an alternative return statement.
That is a useful constraint in a language with many ways to express the same operation. My affection for Perl includes its directness with regular expressions, but that flexibility can make surrounding logic difficult to review. A cleanup block with a defined role reduces one source of ambiguity without changing the text processing expressions themselves.
This remains an experiment as of this release. Programs must opt in with use feature 'try', and Perl emits warnings in the experimental::try category. The opt-in is part of the documented interface, so a snippet that omits it does not accurately represent how to use this syntax today. The development release gives authors a concrete control flow model to examine, including the important case where successful work returns early and still needs its final action executed.