Perl 5.33.7 adds try and catch to the interpreter
Experimental exception syntax changes the meaning of return inside a protected block and gives the caught exception a lexical variable.
A protected block inside a function should be able to return from that function. Perl now has an experimental exception construct with that behaviour. The 5.33.7 release notes introduce native try and catch syntax, enabled through an explicit feature declaration. The consequential change is in control flow, beyond the more familiar spelling of an exception handler.
The syntax manual shipped with this release explains the difference from a block eval. A return within an eval block returns from that block. A return inside the new try exits its containing function. That makes the protected region behave like part of the surrounding function for a common control-flow operation. It also gives developers a specific semantic question to consider when changing existing exception-handling code.
I like Perl for text work where the program can remain short. Exception handling is one of the places where compact code still needs an explicit account of what happens next. A construct whose blocks follow the surrounding function makes that account easier to read, provided its experimental status stays visible.
Catch introduces a lexical variable
The following example uses the syntax documented in 5.33.7. Its positive branch returns directly from the containing function. The handler supplies an alternative result when the protected block throws.
use feature "try";
no warnings "experimental::try";
sub positive_value {
my ($value) = @_;
try {
die "expected a positive value" unless $value > 0;
return $value;
}
catch ($error) {
warn $error;
return undef;
}
}
The catch keyword takes a variable declaration in parentheses. There is no my before $error because the declaration is implicit in this syntax. The variable is lexical to the following handler block and contains the exception thrown by the protected code. This puts the association between an exception and its handler directly into the grammar. The variable does not have to become a name shared by unrelated handlers elsewhere in the function.
An exception can originate in a function called by the protected block. The syntax documentation explicitly includes that case. A handler therefore covers the execution of the block, including uncaught exceptions from those calls. A successful call skips the catch block. After either ordinary completion or a handler that completes normally, execution continues with the next statement after the construct.
That final qualification matters. The handler is ordinary control-flow code with the ability to return from the function. A handler that returns does not resume at the statement following the construct. Reading it as an automatic continuation after an error would miss the same distinction that motivated the new syntax.
Return keeps the function as its target
The manual permits goto and loop-control operations within both blocks as well as return. It says these operations behave as expected without warnings. This is a substantial reason to put exception syntax in the interpreter. The documentation can define how the blocks participate in language control flow instead of requiring a reader to infer that behaviour from a wrapper around a callback.
This release does not establish that every module-based exception idiom has identical semantics. Its release notes describe an initial experimental implementation. An existing handler may depend on where a return stops execution. Replacing syntax without accounting for that behaviour can change the result of a function even if the same exception is caught.
A concrete review question is whether a return inside the old protected region was intended to end the function. The new construct supports that intention directly. If the old code depended on a block producing a value while the function continued, the corresponding new code needs a value expression. That distinction follows from the documented return semantics and does not require speculation about performance or implementation speed.
A handler can produce a value
The versioned manual also documents use as the final statement in a function or a do block. The last evaluated value becomes the result. A handler can then provide a fallback without an explicit return.
use feature "try";
no warnings "experimental::try";
my $value = do {
try {
die "missing input";
}
catch ($error) {
warn $error;
"fallback";
}
};
Here the handler ends in a string expression. That is the value produced through the enclosing do block. Adding return would ask for different control flow, as the manual warns. Expression results and function returns remain separate concepts even though both can appear in the handler. This example deliberately keeps the fallback local so the distinction can be seen without another API or package.
I consider the lexical exception variable the more useful default for explaining a handler. The name tells the reader which value the recovery code examines. Its scope also states where that value belongs. This is a judgement about readability, not a claim that existing exception code must be rewritten.
The feature declaration is part of the example
The syntax requires use feature "try" and emits an experimental::try warning. The examples suppress that specific category so their warning output concerns the deliberately thrown exception. Removing the suppression restores the experimental diagnostic. Disabling that warning does not change the maturity of the language feature or turn the development release into a stable interpreter.
The useful work at this stage is to exercise the semantics the release actually specifies. Protected code can return from its containing function. A handler receives a lexical exception value, and either block can contribute the last evaluated value in an expression context. Those behaviours explain what this initial implementation offers without assuming a later release will preserve every detail.