1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
|
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>
|