We know that a Failure can be handled by a CATCH block.
In the following example we create an 'AdHoc' Failure (in other-sub) and we handle the Exception in a CATCH block (in my-sub)
sub my-sub {
try {
CATCH {
when X::AdHoc { say 'AdHoc Exception handled here'; .resume }
default {say 'Other Exception'; .resume}
}
my $b = other-sub();
$b.so ?? $b.say !! 'This was a Failure'.say;
}
}
sub other-sub { fail 'Failure_X' }
my-sub();
The output is the following:
AdHoc Exception handled here
This was a Failure
My question is though: How can we distinguish between Failure and a "normal" Exception in the CATCH block in order to differentiate between the two cases?
The relationship between Failure
and Exception
is that a Failure
has an Exception
- that is to say, it holds the exception object as part of its state. Something like this:
class Failure {
has Exception $.exception;
# ...
}
When a Failure
"explodes", it does so by throwing the Exception
that is inside of it. Thus, what reaches the CATCH
block is the Exception
object, and there's no link back to the enclosing Failure
. (In fact, a given Exception
object could in principle be held by many Failure
s.)
Therefore, there's no direct way to detect this. From a design perspective, you probably shouldn't be, and should find a different way to solve your problem. A Failure
is just a way to defer the throwing of an exception and allowing for it to be treated as a value; it's not intended that the nature of the underlying problem changes because it's conveyed as a value rather than as an immediate transfer of control flow. Unfortunately, the original goal wasn't stated in the question; you may find it useful to look at control exceptions, but otherwise perhaps post another question about the underlying problem you're trying to solve. There's probably a better way.
For completeness, I'll note that there are indirect ways that one may detect that the Exception
was thrown by a Failure
. For example, if you obtain the .backtrace
of the exception object and look at the top frame's package, it's possible to determine that it comes from the Failure
:
sub foo() { fail X::AdHoc.new(message => "foo") }
try {
foo();
CATCH {
note do { no fatal; .backtrace[0].code.package ~~ Failure };
.resume
}
}
However, this is heavily dependent on implementation details that could easily change, so I'd not rely on it.
Just remove the try
wrapper:
sub my-sub {
# try { <--- remove this line...
CATCH {
when X::AdHoc { say 'AdHoc Exception handled here'; .resume }
default {say 'Other Exception'; .resume}
}
my $b = other-sub();
$b.so ?? $b.say !! 'This was a Failure'.say;
# } <--- ...and this one
}
sub other-sub { fail 'Failure_X' }
my-sub();
You used try
. A try
does a few things, but the pertinent thing here is that it tells Raku to immediately promote any Failure
s in its scope to exceptions -- which is what you say you don't want. So the simplest solution is to just stop doing that.
This answer just verbosely repeats part of jnthn's explanation (see in particular comments he wrote below his answer). But I wasn't convinced all readers would spot/understand this aspect, and didn't think a comment or two on jnthn's answer would help, hence this answer.
I've written this as a community answer to ensure I won't benefit from any upvotes because it obviously doesn't warrant that. If it gets enough downvotes we'll just delete it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With