Haxe allows catching values using its try/catch
syntax:
try try-expr catch (varName1:Type1) catch-expr-1 catch (varName2:Type2) catch-expr-2
If during runtime the evaluation of try-expression
causes a throw
, it can be caught by any subsequent catch
block. These blocks consist of
Haxe allows throwing and catching any kind of value, it is not limited to types inheriting from a specific exception or error class. However since Haxe 4.1.0 it's highly recommended to throw and catch only instances of haxe.Exception
and its descendants.
Catch blocks are checked from top to bottom with the first one whose type is compatible with the thrown value being picked.
This process has many similarities to the compile-time unification behavior. However, since the check has to be done at runtime there are several restrictions:
Instead of Dynamic
and Any
it's possible (and recommended) to omit the type hint for wildcard catches:
try { doSomething(); } catch(e) { //All exceptions will be caught here trace(e.message); }
This is equivalent to catch(e:haxe.Exception)
.
Prior to Haxe 4.1.0 the only way to catch all exceptions is by using Dynamic
or Any
as the catch type.
To get a string representation of the exception Std.string(e)
could be used.
try { doSomething(); } catch(e:Any) { // All exceptions will be caught here trace(Std.string(e)); }
If the catch type is haxe.Exception
or one of its descendants, then the exception stack is available in the stack
property of the exception instance.
try { doSomething(); } catch(e:haxe.Exception) { trace(e.stack); }
The exception call stack is available via haxe.CallStack.exceptionStack()
inside of a catch
block:
try { doSomething(); } catch(e:Dynamic) { var stack = haxe.CallStack.exceptionStack(); trace(haxe.CallStack.toString(stack)); }
Even if an instance of haxe.Exception
is thrown again, it still preserves all the original information, including the stack.
import haxe.Exception; class Main { static function main() { try { try { doSomething(); } catch(e:Exception) { trace(e.stack); throw e; //rethrow } } catch(e:Exception) { trace(e.stack); } } static function doSomething() { throw new Exception('Terrible error'); } }
This example being executed with haxe --main Main --interp
would print something like this:
Main.hx:13:
Called from Main.doSomething (Main.hx line 11 column 15)
Called from Main.main (Main.hx line 5 column 5)
Main.hx:17:
Called from Main.doSomething (Main.hx line 11 column 15)
Called from Main.main (Main.hx line 5 column 5)
The compiler may avoid unnecessary wrapping when throwing native exceptions and handle this at the catch-site instead. This ensures that any exception (native or otherwise) can be caught with catch (e:haxe.Exception)
. This also applies for rethrowing exceptions.
For example here's a Haxe code, which being compiled to PHP target catches and rethrows all exceptions in the inner try/catch
. And rethrown exceptions are still catchable using their target native types:
try { try { (null:Dynamic).callNonExistentMethod(); } catch(e:Exception) { trace('Haxe exception: ' + e.message); throw e; //rethrow } } catch(e:php.ErrorException) { trace('Rethrown native exception: ' + e.getMessage()); }
This sample being compiled to PHP target would print:
Main.hx:9: Haxe exception: Trying to get property 'callNonExistentMethod' of non-object
Main.hx:13: Rethrown native exception: Trying to get property 'callNonExistentMethod' of non-object
Sometimes it's convenient to chain exceptions instead of throwing the same exception instance again. To do so just pass an exception to a new exception instance:
try { doSomething(); } catch(e:haxe.Exception) { cleanup(); throw new haxe.Exception('Failed to do something', e); }
Being executed with --interp
this sample would print a message like this:
Main.hx:12: characters 7-12 : Uncaught exception Exception: Terrible error
Called from Main.doSomething (Main.hx line 10 column 13)
Next Exception: Failed to do something
Called from Main.doSomething (Main.hx line 12 column 13)
Called from Main.main (Main.hx line 5 column 5)
Main.hx:5: characters 5-18 : Called from here
One use-case is to make error logs more readable.
Chained exceptions are available through previous
property of haxe.Exception
instances:
try { try { doSomething(); } catch(e:haxe.Exception) { cleanup(); throw new haxe.Exception('Failed to do something', e); } } catch(e:haxe.Exception) { trace(e.message); // "Failed to do something" trace(e.previous.message); // "Terrible error" }
Another use-case is creating a library, which does not expose internal exceptions as public API, but still provides information about exceptions reasons:
import haxe.Exception; class MyLibException extends Exception {} class MyLib { static public function calculateSomething() { try { heavyCalculation(); } catch(e:Exception) { throw new MyLibException(e.message, e); } } static function heavyCalculation() {} }
Now library users don't have to worry about specific arithmetic exceptions. All they need to do is handle MyLibException
.