A try statement allows a program to regain control following a normally fatal runtime error.
try
<block>
catch e
<block>
end try
In addition the builtin routine
throw() can be used to transfer control directly to the catch block,
potentially from within several nested routine calls.
The catch statement traps an exception within the try block, or any routines it invokes, that is not first caught by another nested try/catch statement.
Any exceptions that must be re-thrown must be done so explicitly, and unlike other languages there is only one (non-optional) catch clause.
There are no confusing
classes of exceptions, and no implicit filtering, and hence no unexpected leaks to the outside world.
The exception variable is declared immediately after the catch keyword and is a
sequence.
It can be given any valid identifier as a name, though "e" is usually sufficient.
For details of the contents of the exception variable, see
throw().
Just like the loop control variables of for statements, the exception variable can be predeclared and then persists after the end try,
otherwise it is automatically declared and drops out of scope on the end try.
pwa/p2js: Supported, however note that JavaScript does
not (for instance)
throw typecheck errors. While it is perfectly acceptable to use try/catch to aid debugging on desktop/Phix first, or handle rare and
unusual cases with an explicit throw(), any program which
relies on a specific exception (especially implicit) triggering for
successful operation is fairly unlikely to work consistently under pwa/p2js.
See Also:
|
throw
|
Technicalia
|
Note that a try/catch statement may interfere with your ability to debug code within the try block (although trace and printf are still fine).
It can certainly negate the idea of a fail fast development methodology, should the program quietly soldier on
rather than immediately alert you to a problem and force you to fix it.
The authors of the Go programming language (and certainly neither Rob Pike nor Ken Thompson can be considered amateurs) at first
completely omitted exceptions, but eventually (after some probably quite relentless pressure) added a panic/recover mechanism;
clearly they had their reservations, so perhaps you should too.
There is no guarantee that all exceptions can be caught: rogue code that corrupts the stack will make it impossible to even look for a suitable handler.
The abort() and crash() routines now invoke throw() when an exception handler is detected, however the equivalent of the former in
non-phix code (ie kernel32.dll/ExitProcess or sys_exit from a dll/so or inline assembly) cannot be caught (by any means that I know or care of).
Also, passing invalid arguments to throw() (e not atom when user_data not {}, or wrong-type-elements) is deemed to be non-catchable.
Apart from that I am not currently aware of any errors, or any types or classes of error, that can never be caught.
(In at least one release a try statement even had the power to prevent you from quitting a debug/trace session; it no longer re-throws e12pa,
ie '!' keyed.)
There is no type checking on a catch clause, since throw() always provides a sequence, hence
user defined types are not permitted on predeclared exception variables, besides you probably don’t
really be wanting to confuse yourself or waste time trying to catch exceptions in catch clauses if you can at all avoid it.
Obviously the compiler/runtime simply cannot distinguish the genuinely exceptional ’blimey, did not expect that’ from the
mundane ’simple typo or other glaring error’, and must therefore treat them identically - instead it is entirely the programmers
responsibility to make that distinction themselves in each and every application and catch clause statement therein.
If you ever find yourself thinking that the runtime should quietly deal with those but tell you about these, then you are probably crediting it
with way too much intelligence. See point 1 below.
The catch clause is actually opTryend (ie restore the previous exception handler, if any), then a jump to the end try, and then the actual catch
handler itself (which also restores the previous exception handler, if any). Leaving a blank (or comment-only) line before the catch keyword may
make some listing files (-d command line option) slightly easier to follow.
Compilation issues the error "invalid (circumvents try handler reset)" when appropriate.
The try block may not contain any exit/break/continue statements that would circumvent the reset of the handler. Said compilation error can/shd
be avoided by something like bool bPanic = false; try ... bPanic = true ... end try; if bPanic then exit/break/continue .
In contrast, return statements are perfectly valid, as are any exit/break/continue statements within the catch block, or wholly nested within
(/not leaking from) the try block.
- aside:
- Note the use of #ilASM{} to effect a jump of that ilk is not protected against, but it will probably go horribly wrong, should
anything untoward happen, especially before a return discards the handler naturally, or a containing try effects a higher-level handler reset.
The system stack is rebalanced along with the exception hander reset, mainly in case something happened deep inside a c_func/proc/call_back, and
hence jumping to the "wrong" handler may very well unbalance the system stack. (The triggering of a higher level catch may help.)
Compilation issues "Warning: empty catch block" messages when appropriate.
A blanket "ignore all" handler in the top-level main loop of an application may sound like an easy way to make your code "robust".
However it also risks making you completely blind towards any emerging problems - which can in some cases be perfectly fine - if a
"jumping giraffe" game tries to draw the eyeballs off-screen, no-one cares (though one might well ask, "why are you throwing an execption then?"),
but a bug in the compiler (for instance) is quite different.
At the very least you should consider something like if DEBUG then ?9/0 end if and in that way your live customers
can experience the trouble-free life you think they deserve, while your development staff are kept honest and immediately informed
of any developing issues, and forced to address them.
Be warned that "trouble-free life" may turn into the classic headbutting a brick wall experience of no clue as to what is going wrong
- a log file of otherwise quietly ignored exceptions may help, but if that collects 30 million of them it is your problem not mine.
I have four recommendations (which may overlap and overpreach a bit):
- Write the code without a try container and see how far you get. Typically something like a subscript error can and should be
fixed immediately, with the try statement being intended to guard against something rather more sinister and perhaps intermittent.
If not careful, a try statement will mask errors you would much rather it did not.
- Always try and localise a try statement as much as possible, to cover the least amount of code and
that way (only) solve a very specific problem.
Your catch clauses will obviously be much easier to write, and later read and understand.
While a catch clause can be used to "common up" error handling, do not go overboard.
- Use try statements to deal with third-party code you cannot or would rather not modify, as
opposed to permission for your own sloppily-written abomination™.
A perfect example of the former: display "unable to play video" rather than let a corrupt file
euthanise your entire application.
- Start a new catch clause as
?e , then gradually add conditions and filters, with
comments, for specific cases where a "do nothing" response is genuinely the best thing to do.
Let everything you have not yet seen show up, including that bug you will probably introduce sometime
next week.
|