399 lines
14 KiB
Plaintext
399 lines
14 KiB
Plaintext
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.
|
|
|
|
Syntax similar to this module has now been added to core perl, starting
|
|
at version 5.34.0. If you are writing new code, it is suggested that
|
|
you instead use the Feature::Compat::Try module instead, as that will
|
|
enable the core feature on those supported perl versions, falling back
|
|
to Syntax::Keyword::Try on older perls.
|
|
|
|
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 ); # 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.
|
|
|
|
Even though it parses as a statement and not an expression, a try block
|
|
can still yield a value if it appears as the final statement in its
|
|
containing sub or do block. For example:
|
|
|
|
my $result = do {
|
|
try { attempt_func() }
|
|
catch ($e) { "Fallback Value" }
|
|
};
|
|
|
|
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.
|
|
|
|
OTHER MODULES
|
|
|
|
There are already quite a number of modules on CPAN that provide a
|
|
try/catch-like syntax for Perl.
|
|
|
|
* Try
|
|
|
|
* TryCatch
|
|
|
|
* Try::Tiny
|
|
|
|
* Syntax::Feature::Try
|
|
|
|
In addition, core perl itself gained a try/catch syntax based on this
|
|
module at version 5.34.0. It is available as use feature 'try'.
|
|
|
|
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.
|
|
|
|
The core feature 'try' is also implemented as true native syntax in the
|
|
perl parser.
|
|
|
|
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.
|
|
|
|
The core feature 'try' also behaves in this manner.
|
|
|
|
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.
|
|
|
|
It also behaves this way using the core feature 'try'.
|
|
|
|
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.
|
|
|
|
These also work fine when using the core feature 'try'.
|
|
|
|
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. You
|
|
cannot assign from the result of a try block. A common workaround is to
|
|
wrap the try/catch statement inside a do block, where its final
|
|
expression can be captured and used as a value.
|
|
|
|
The same do block wrapping also works for the core feature 'try'.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
The core feature 'try' does not implement finally at all, and also
|
|
requires that every try block be followed by a 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.
|
|
|
|
Likewise the core feature 'try' currently does not provide this
|
|
ability, though it remains an area of ongoing design work.
|
|
|
|
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
|
|
|
|
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
|
|
used 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 localised.
|
|
|
|
However, when using the experimental value-yielding expression version
|
|
try do {...} on perl versions 5.22 or older, the localisation 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>
|
|
|