NAME
Syntax::Keyword::Try
- a try/catch/finally
syntax for perl
SYNOPSIS
use Syntax::Keyword::Try;
sub foo {
try {
attempt_a_thing();
return "success";
}
catch ($e) {
warn "It failed - $e";
return "failure";
}
}
DESCRIPTION
This module provides a syntax plugin that implements exception-handling semantics in a form familiar to users of other languages, being built on a block labeled with the try
keyword, followed by at least one of a catch
or finally
block.
As well as providing a handy syntax for this useful behaviour, this module also serves to contain a number of code examples for how to implement parser plugins and manipulate optrees to provide new syntax and behaviours for perl code.
Experimental Features
Some of the features of this module are currently marked as experimental. They will provoke warnings in the experimental
category, unless silenced.
You can silence this with no warnings 'experimental'
but then that will silence every experimental warning, which may hide others unintentionally. For a more fine-grained approach you can instead use the import line for this module to only silence this module's warnings selectively:
use Syntax::Keyword::Try qw( try :experimental(typed) );
use Syntax::Keyword::Try qw( try :experimental(try_value) );
use Syntax::Keyword::Try qw( try :experimental ); # all of the above
Don't forget to import the main try
symbol itself, to activate the syntax.
KEYWORDS
try
try {
STATEMENTS...
}
...
A try
statement provides the main body of code that will be invoked, and must be followed by either a catch
statement, a finally
statement, or both.
Execution of the try
statement itself begins from the block given to the statement and continues until either it throws an exception, or completes successfully by reaching the end of the block. What will happen next depends on the presence of a catch
or finally
statement immediately following it.
The body of a try {}
block may contain a return
expression. If executed, such an expression will cause the entire containing function to return with the value provided. This is different from a plain eval {}
block, in which circumstance only the eval
itself would return, not the entire function.
The body of a try {}
block may contain loop control expressions (redo
, next
, last
) which will have their usual effect on any loops that the try {}
block is contained by.
The parsing rules for the set of statements (the try
block and its associated catch
and finally
) are such that they are parsed as a self- contained statement. Because of this, there is no need to end with a terminating semicolon.
Note (especially to users of Try::Tiny and similar) that the try {}
block itself does not necessarily stop exceptions thrown inside it from propagating outside. It is the presence of a later catch {}
block which causes this to happen. A try
with only a finally
and no catch
will still propagate exceptions up to callers as normal.
catch
...
catch ($var) {
STATEMENTS...
}
or
...
catch {
STATEMENTS...
}
A catch
statement provides a block of code to the preceding try
statement that will be invoked in the case that the main block of code throws an exception. Optionally a new lexical variable can be provided to store the exception in. If not provided, the catch
block can inspect the raised exception by looking in $@
instead.
Presence of this catch
statement causes any exception thrown by the preceding try
block to be non-fatal to the surrounding code. If the catch
block wishes to optionally handle some exceptions but not others, it can re-raise it (or another exception) by calling die
in the usual manner.
As with try
, the body of a catch {}
block may also contain a return
expression, which as before, has its usual meaning, causing the entire containing function to return with the given value. The body may also contain loop control expressions (redo
, next
or last
) which also have their usual effect.
If a catch
statement is not given, then any exceptions raised by the try
block are raised to the caller in the usual way.
catch (Typed)
...
catch ($var isa Class) { ... }
...
catch ($var =~ m/^Regexp match/) { ... }
Experimental; since version 0.15.
Optionally, multiple catch statements can be provided, where each block is given a guarding condition, to control whether or not it will catch particular exception values. Use of this syntax will provoke an experimental
category warning on supporting perl versions, unless silenced by importing the :experimental(typed)
tag (see above).
Two kinds of condition are supported:
-
catch ($var isa Class)
The block is invoked only if the caught exception is a blessed object, and derives from the given package name.
On Perl version 5.32 onwards, this condition test is implemented using the same op type that the core
$var isa Class
syntax is provided by and works in exactly the same way.On older perl versions it is emulated by a compatibility function. Currently this function does not respect a
->isa
method overload on the exception instance. Usually this should not be a problem, as exception class types rarely provide such a method. -
catch ($var =~ m/regexp/)
The block is invoked only if the caught exception is a string that matches the given regexp.
When an exception is caught, each condition is tested in the order they are written in, until a matching case is found. If such a case is found the corresponding block is invoked, and no further condition is tested. If no contional block matched and there is a default (unconditional) block at the end then that is invoked instead. If no such block exists, then the exception is propagated up to the calling scope.
finally
...
finally {
STATEMENTS...
}
A finally
statement provides a block of code to the preceding try
statement (or try/catch
pair) which is executed afterwards, both in the case of a normal execution or a thrown exception. This code block may be used to provide whatever clean-up operations might be required by preceding code.
Because it is executed during a stack cleanup operation, a finally {}
block may not cause the containing function to return, or to alter the return value of it. It also cannot see the containing function's @_
arguments array (though as it is block scoped within the function, it will continue to share any normal lexical variables declared up until that point). It is protected from disturbing the value of $@
. If the finally {}
block code throws an exception, this will be printed as a warning and discarded, leaving $@
containing the original exception, if one existed.
VALUE SEMANTICS
Warning: the feature described in this section is experimental. This experiment may be stablised in a later version, or may be altered or removed without further notice. It is present here for testing and evaluation purposes.
Additionally, on perl versions 5.18 and later, it will produce a warning in the experimental
category.
The syntax provided by this module may be used as a value-yielding expression. Because this syntax is new, experimental, and somewhat surprising, it must be specifically requested by name try_value
:
use Syntax::Keyword::Try qw( try try_value );
my $result = try do { ... } catch { ... };
Also, on Perl versions 5.24 and later:
my $result = try do { ... } finally { ... };
my $result = try do { ... } catch { ... } finally { ... };
Specifically, note that the expression must be spelled as try do { ... }
so that the syntax is distinct from that used by control-flow statements. The interposed do
keyword reminds the reader, and instructs the syntax parser, that this will be an expression, not a statement. It is not necessary to similarly notate the catch
or finally
blocks.
In this case, the syntax behaves syntactically like an expression, and may appear anywhere a normal expression is allowed. It follows similar semantics to the purely control-flow case; if the code in the try
block does not throw an exception, then the expression as a whole yields whatever value the try
expression did. If it fails, then the catch
block is executed and the expression yields its resulting value instead. A finally
block, if present, will be evaluated for side-effects before the rest of the expression returns.
Remember that, as in the control-flow case, the return
keyword will cause the entire containing function to return, not just the try
block.
OTHER MODULES
There are already quite a number of modules on CPAN that provide a try/catch
-like syntax for Perl.
They are compared here, by feature:
True syntax plugin
Like Try and Syntax::Feature::Try, this module is implemented as a true syntax plugin, allowing it to provide new parsing rules not available to simple functions. Most notably here it means that the resulting combination does not need to end in a semicolon.
In comparison, Try::Tiny is plain perl and provides its functionality using regular perl functions; as such its syntax requires the trailing semicolon.
TryCatch is a hybrid that uses Devel::Declare to parse the syntax tree.
@_
in a try or catch block
Because the try
and catch
block code is contained in a true block rather than an entire anonymous subroutine, invoking it does not interfere with the @_
arguments array. Code inside these blocks can interact with the containing function's array as before.
This feature is unique among these modules; none of the others listed have this ability.
return
in a try or catch block
Like TryCatch and Syntax::Feature::Try, the return
statement has its usual effect within a subroutine containing syntax provided by this module. Namely, it causes the containing sub
itself to return.
In comparison, using Try or Try::Tiny mean that a return
statement will only exit from the try
block.
next
/last
/redo
in a try or catch block
The loop control keywords of next
, last
and redo
have their usual effect on dynamically contained loops.
Syntax::Feature::Try documents that these do not work there. The other modules make no statement either way.
Value Semantics
Like Try and Syntax::Feature::Try, the syntax provided by this module only works as a syntax-level statement and not an expression when the experimental try_value
feature described above has not been enabled. You cannot assign from the result of a try
block. Additionally, final-expression value semantics do not work, so it cannot be contained by a do
block to yield this value.
In comparison, the behaviour implemented by Try::Tiny can be used as a valued expression, such as assigned to a variable or returned to the caller of its containing function. Such ability is provided by this module if the experimental try_value
feature is enabled, though it must be spelled differently as try do { ... }
.
try
without catch
Like Syntax::Feature::Try, the syntax provided by this module allows a try
block to be followed by only a finally
block, with no catch
. In this case, exceptions thrown by code contained by the try
are not suppressed, instead they propagate as normal to callers. This matches the behaviour familiar to Java or C++ programmers.
In comparison, the code provided by Try and Try::Tiny always suppress exception propagation even without an actual catch
block.
The TryCatch module does not allow a try
block not followed by catch
.
Typed catch
Try and Try::Tiny make no attempt to perform any kind of typed dispatch to distinguish kinds of exception caught by catch
blocks.
TryCatch and Syntax::Feature::Try both attempt to provide a kind of typed dispatch where different classes of exception are caught by different blocks of code, or propagated up entirely to callers.
This module provides such an ability, via the currently-experimental catch (VAR cond...)
syntax.
The design thoughts continue on the RT ticket https://rt.cpan.org/Ticket/Display.html?id=123918.
WITH OTHER MODULES
Future::AsyncAwait
As of Future::AsyncAwait
version 0.10 and Syntax::Keyword::Try version 0.07, cross-module integration tests assert that basic try/catch
blocks inside an async sub
work correctly, including those that attempt to return
from inside try
.
use Future::AsyncAwait;
use Syntax::Keyword::Try;
async sub attempt
{
try {
await func();
return "success";
}
catch {
return "failed";
}
}
ISSUES
Context propagation during return
A return
statement inside a try
block will currently always propagate a scalar context, even if the function it appears in itself is in list context.
sub inner
{
return wantarray ? (qw( a list of things )) : "a single scalar";
}
sub outer
{
try { return inner() }
catch {}
}
my @result = outer();
print for @result; # prints "a single scalar"
This is discussed at https://rt.cpan.org/Ticket/Display.html?id=124229.
Thread-safety at load time cannot be assured before perl 5.16
On perl versions 5.16 and above this module is thread-safe.
On perl version 5.14 this module is thread-safe provided that it is use
d before any additional threads are created.
However, when using 5.14 there is a race condition if this module is loaded late in the program startup, after additional threads have been created. This leads to the potential for it to be started up multiple times concurrently, which creates data races when modifying internal structures and likely leads to a segmentation fault, either during load or soon after when more code is compiled.
As a workaround, for any such program that creates multiple threads, loads additional code (such as dynamically-discovered plugins), and has to run on 5.14, it should make sure to
use Syntax::Keyword::Try;
early on in startup, before it spins out any additional threads.
(See also https://rt.cpan.org/Public/Bug/Display.html?id=123547)
$@ is not local'ised by try do
before perl 5.24
On perl versions 5.24 and above, or when using only control-flow statement syntax, $@
is always correctly local
ised.
However, when using the experimental value-yielding expression version try do {...}
on perl versions 5.22 or older, the local
isation of $@
does not correctly apply around the expression. After such an expression, the value of $@
will leak out if a failure happened and the catch
block was invoked, overwriting any previous value that was visible there.
(See also https://rt.cpan.org/Public/Bug/Display.html?id=124366)
ACKNOWLEDGEMENTS
With thanks to Zefram
, ilmari
and others from irc.perl.org/#p5p
for assisting with trickier bits of XS logic.
AUTHOR
Paul Evans <leonerd@leonerd.org.uk>