Distinguish between Exception and Failure in a CATCH block [ RAKU ]

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 Failures 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.


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 Failures.)

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.