Definition: | throw(object e, object user_data={}) |
Description: |
Transfer control immediately to some containing try/catch statement, even if it is several levels lower down the call stack, or
terminate the program if no such handler exists.
e: typically an integer or string, but it can also be a complete exception in the re-throw case. user_data: if not {} (or 0), then e must be an atom. It can contain anything that you might find helpful in the handler(/logfile, etc). |
Comments: |
The throw routine builds an exception and passes it to an appropriate handler.
The short version is: An exception is a sequence of run-time diagnostic information, with the offending instruction address converted to a human-readable source file/line number.The long version is: The following constants (automatically defined in psym.e/syminit) can be used to examine the contents of an exception:Traditional fatal runtime errors are mapped as follows (with E_ADDR..E_LINE also pre-populated, and E_NAME..E_PATH -1’d as above): throw(1,"type check error, %s is %s") throw(2,"attempt to divide by 0") ... throw(122,"invalid poke size") See builtins\VM\pDiagN.e for the full/up-to-date list. In the catch clause, e[E_CODE] will be 1..122 and e[E_USER] will be a (post-sprintf) string. Obviously 55/"unhandled exception" is not and should not be mapped, and if you get one and then try to write a handler to catch it, you might be slightly disappointed, though you should be able to catch the error actually thrown (which could technically be a fake 55/"unhandled exception") rather than get the (real/from pDiagN.e) error 55 being reported on the throw statement (and your application terminated). Likewise 12/"program aborted" (ie '!' keyed in the trace window) is not automatically re-thrown, and hence cannot be caught. Sometimes you may need to re-throw an exception, and let some containing catch process it - which is as simple and straightforward as it could possibly ever be: try ... catch e if <recognised> then <handle it here> else throw(e) end if end try All details are preserved, which also means that you can make up a completely fake exception, with made-up a routine name, line, source file, etc. I don’t particularly recommend that; while I can imagine cases where you specifically want to catch "that exception from line 851", it would almost certainly be better to use a unique error code when possible. Compilation may issue "Error: without debug in force" messages, since that option suppresses the tables needed for machine address->source code line number mapping. There is no absolute guarantee that the contents of an exception will be meaningful: any of e[E_RTN..E_PATH] may be left as -1, e[E_USER] may or may not exist, and length(e) may be greater than 8. The catch clause should be coded accordingly/defensively. Obviously the application will terminate immediately, just like /0, should throw() be invoked when there is no active handler (try/catch construct) in the current call stack, and likewise in that case the above mapping to throw(1..122) does not occur. |
Performance: |
Exception handling is designed to minimise the impact on code which does /not/ trigger it, and is intended for very infrequent events. Should you compare the performance overhead of throw("failure") with return false then unfortunately you
are in for a very big shock.Building detailed run-time diagnostic information and converting a machine address into a human-readable line number, file, and routine name, is simply always going to take considerably longer than a single "mov eax,ebx" instruction!
Should you need to process 10,000 items, but 2 of them trigger an exception, the savings of simplified code (such as not checking for division by zero, whether a file of that name already exists, etc) on 9,998 of them may very well significantly outweigh the cost of the two exceptions that are triggered. It is in that sense, alone, that appropriate use of exceptions can make some programs noticeably faster. However, the wilful and excessive use of throw() in quite unnecessary situtations will almost inevitably result in a slower program. That said, any code not being iterated many thousands of times is better off being as clear and intuitive as you can make it. |
Example: |
try integer i = 1/0 -- throw(2,"attempt to divide by 0") -- equivalent/as triggered via pDiagN.e/diag(). catch e ?{"raw:",e} if length(e)>=E_USER and string(e[E_USER]) and string(e[E_FILE]) and e[E_LINE]!=-1 then printf(1,"%s (%s line %d)\n",{e[E_USER],e[E_FILE],e[E_LINE]}) else ?{"oops... (test.exw line 12)",e} end if end try Output: {"raw:",{2,8249772,2,21,"-1","test.exw","C:\\Program Files (x86)\\Phix\\","attempt to divide by 0"}} attempt to divide by 0 (test.exw line 2) While the above conditions/printf should give you the basic idea of what may be required, be advised that that using cut-and-paste may cause unforseen problems, since the precise content of an exception can vary quite dramatically. Although those four tests on 'e' cover everything I can think of right now, creative use of throw() is quite likely to find a way to make that catch clause as it stands do the completely wrong thing. |
See Also: | try/catch, crash_file, crash_message, crash_routine, era |
