Using exceptions in Raku
Exceptions in Raku are objects that hold information about errors. An error can be, for example, the unexpected receiving of data or a network connection no longer available, or a missing file. The information that an exception object stores is, for instance, a human-readable message about the error condition, the backtrace of the raising of the error, and so on.
All built-in exceptions inherit from Exception, which provides some basic behavior, including the storage of a backtrace and an interface for the backtrace printer.
Ad hoc exceptions can be used by calling die with a description of the error:
die "oops, something went wrong";# OUTPUT: «oops, something went wrong in block <unit> at my-script.p6:1␤»
It is worth noting that
die prints the error message to the standard error
Typed exceptions provide more information about the error stored within an exception object.
For example, if while executing
.zombie copy on an object, a needed path
foo/bar becomes unavailable, then an X::IO::DoesNotExist exception can be raised:
die X::IO::DoesNotExist.new(:path("foo/bar"), :trying("zombie copy"))# OUTPUT: «Failed to find 'foo/bar' while trying to do '.zombie copy'# in block <unit> at my-script.p6:1»
Note how the object has provided the backtrace with information about what went wrong. A user of the code can now more easily find and correct the problem.
It's possible to handle exceptional circumstances by supplying a
die X::IO::DoesNotExist.new(:path("foo/bar"), :trying("zombie copy"));CATCH# OUTPUT: «some kind of IO exception was caught!»
Here, we are saying that if any exception of type
X::IO occurs, then the message
some kind of IO exception was caught! will be sent to stderr, which is what
$*ERR.say does, getting displayed on whatever constitutes the standard error device in that moment, which will probably be the console by default.
CATCH block uses smartmatching similar to how
given/when smartmatches on options, thus it's possible to catch and handle various categories of exceptions inside a
To handle all exceptions, use a
default statement. This example prints out almost the same information as the normal backtrace printer.
Note that the match target is a role. To allow user defined exceptions to match in the same manner, they must implement the given role. Just existing in the same namespace will look alike but won't match in a
After a CATCH has handled the exception, the block enclosing the
CATCH block is exited.
In other words, even when the exception is handled successfully, the rest of the code in the enclosing block will never be executed.
die "something went wrong ...";CATCHsay "This won't be said."; # but this line will be never reached since# the enclosing block will be exited immediately# OUTPUT: «something went wrong ...␤»
Compare with this:
CATCHsay "Hi! I am at the outer block!"; # OUTPUT: «Hi! I am at the outer block!␤»
See Resuming of exceptions, for how to return control back to where the exception originated.
try block is a normal block which implicitly turns on the
use fatal pragma and includes an implicit
CATCH block that drops the exception, which means you can use it to contain them. Caught exceptions are stored inside the
$! variable, which holds a value of type
A normal block like this one will simply fail:
# OUTPUT: «Failure␤»
try block will contain the exception and put it into the
tryif $! # OUTPUT: «Something failed!␤»say $!.^name; # OUTPUT: «X::Str::Numeric␤»
Any exception that is thrown in such a block will be caught by a
CATCH block, either implicit or provided by the user. In the latter case, any unhandled exception will be rethrown. If you choose not to handle the exception, they will be contained by the block.
is Exceptiontrysay "I'm alive!";try
Which would output:
I'm alive!No, I expect you to DIE Mr. Bond!I'm immortal.Just stop already!in block <unit> at exception.p6 line 21
CATCH block is handling just the
X::AdHoc exception thrown by the
die statement, but not the
E exception. In the absence of a
CATCH block, all exceptions will be contained and dropped, as indicated above.
resume will resume execution right after the exception has been thrown; in this case, in the
die statement. Please consult the section on resuming of exceptions for more information on this.
try-block is a normal block and as such treats its last statement as the return value of itself. We can therefore use it as a right-hand side.
say try // "oh no"; # OUTPUT: «99999␤»say try // "oh no"; # OUTPUT: «oh no␤»
Try blocks support
else blocks indirectly by returning the return value of the expression or Nil if an exception was thrown.
with try +"♥"else# OUTPUT: «not my number!␤»
try can also be used with a statement instead of a block, that is, as a statement prefix:
say try "some-filename.txt".IO.slurp // "sane default";# OUTPUT: «sane default␤»
try actually causes is, via the
use fatal pragma, an immediate throw of the exceptions that happen within its scope, but by doing so the
CATCH block is invoked from the point where the exception is thrown, which defines its scope.
my = "333";sub bad-subtry# OUTPUT: «Error 111 X::AdHoc: Something bad happened␤»
Exceptions can be thrown explicitly with the
.throw method of an
This example throws an
AdHoc exception, catches it and allows the code to continue from the point of the exception by calling the
"OBAI".say;# OUTPUT: «OHAI␤OBAI␤»
CATCH block doesn't match the exception thrown, then the exception's payload is passed on to the backtrace printing mechanism.
"OBAI".say;# OUTPUT: «foo# in block <unit> at my-script.p6:1»
This next example doesn't resume from the point of the exception. Instead, it continues after the enclosing block, since the exception is caught, and then control continues after the
"OBAI".say;# OUTPUT: «OBAI␤»
throw can be viewed as the method form of
die, just that in this particular case, the sub and method forms of the routine have different names.
Exceptions interrupt control flow and divert it away from the statement following the statement that threw it. Any exception handled by the user can be resumed and control flow will continue with the statement following the statement that threw the exception. To do so, call the method
.resume on the exception object.
CATCH # this is step 2die "We leave control after this."; # this is step 1say "We have continued with control flow."; # this is step 3
Resuming will occur right after the statement that has caused the exception, and in the innermost call frame:
sub bad-sub# OUTPUT:# Error X::AdHoc: Something bad happened# Returned not returning
In this case,
.resume is getting to the
return statement that happens right after the
die statement. Please note that the assignment to
$return is taking no effect, since the
CATCH statement is happening inside the call to
bad-sub, which, via the
return statement, assigns the
not returning value to it.
If an exception is thrown and not caught, it causes the program to exit with a non-zero status code, and typically prints a message to the standard error stream of the program. This message is obtained by calling the
gist method on the exception object. You can use this to suppress the default behavior of printing a backtrace along with the message:
is X::AdHocdie X::WithoutLineNumber.new(payload => "message")# prints "message\n" to $*ERR and exits, no backtrace
Control exceptions are raised when throwing an Exception which does the X::Control role (since Rakudo 2019.03). They are usually thrown by certain keywords and are handled either automatically or by the appropriate phaser. Any unhandled control exception is converted to a normal exception.
# OUTPUT: «X::ControlFlow::Return: Attempt to return outside of any Routine␤»# was CX::Return