BrowserREPL-button-tip-on BrowserREPL-button-tip-off

EditButtonTip 2024-07-13

class Failure

Delayed exception

class Failure is Nil { }
Code

A Failure is a soft or unthrown Exception, usually generated by calling &fail. It acts as a wrapper around an Exception object.

Sink (void) context causes a Failure to throw, i.e. turn into a regular exception. The use fatal pragma causes this to happen in all contexts within the pragma's scope. Inside try blocks, use fatal is automatically set, and you can disable it with no fatal.

That means that Failures are generally only useful in cases of code that normally would produce an rvalue; Failures are more or less equivalent to Exceptions in code that will frequently be called in sink context (i.e., for its side-effects, such as with say).

Similarly, you should generally use &fail only inside code that is normally expected to return something.

Checking a Failure for truth (with the Bool method) or definedness (with the defined method) marks the failure as handled, and causes it not to throw in sink context anymore.

You can call the handled method to check if a failure has been handled.

Calling methods on unhandled failures propagates the failure. The specification says the result is another Failure; in Rakudo it causes the failure to throw.

Because a Failure is Nil, which is undefined, a common idiom for safely executing code that may fail uses a with/else statement:

sub may_fail( --> Numeric:D ) {
  my $value = (^10).pick || fail "Zero is unacceptable";
  fail "Odd is also not okay" if $value % 2;
  return $value;
}

with may_fail() -> $value { # defined, so didn't fail
  say "I know $value isn't zero or odd."
} else { # undefined, so failed, and the Failure is the topic
  say "Uh-oh: {.exception.message}."
}
Code

Methods§

method new§

multi method new(Failure:D:)
multi method new(Failure:U:)
multi method new(Failure:U: Exception:D \exception)
multi method new(Failure:U: $payload)
multi method new(Failure:U: |cap (*@msg))
Code

Returns a new Failure instance with payload given as argument. If called without arguments on a Failure object, it will throw; on a type value, it will create an empty Failure with no payload. The latter can be either an Exception or a payload for an Exception. A typical payload would be a Str with an error message. A list of payloads is also accepted.

my $e = Failure.new(now.DateTime, 'WELP‼');
say $e;
CATCH{ default { say .^name, ': ', .Str } }
# OUTPUT: «X::AdHoc: 2017-09-10T11:56:05.477237ZWELP‼␤»
Code

method handled§

method handled(Failure:D: --> Bool:D) is rw
Code

Returns True for handled failures, False otherwise.

sub f() { fail }; my $v = f; say $v.handled; # OUTPUT: «False␤»
Code

The handled method is an lvalue, see routine trait is rw, which means you can also use it to set the handled state:

sub f() { fail }
my $v = f;
$v.handled = True;
say $v.handled; # OUTPUT: «True␤»
Code

method exception§

method exception(Failure:D: --> Exception)
Code

Returns the Exception object that the failure wraps.

sub failer() { fail };
my $failure = failer;
my $ex = $failure.exception;
put "$ex.^name(): $ex";
# OUTPUT: «X::AdHoc: Failed␤»
Code

method self§

method self(Failure:D: --> Failure:D)
Code

If the invocant is a handled Failure, returns it as is. If not handled, throws its Exception. Since Mu type provides .self for every class, calling this method is a handy way to explosively filter out Failures:

my $num1 = '♥'.Int;
# $num1 now contains a Failure object, which may not be desirable

my $num2 = '♥'.Int.self;
# .self method call on Failure causes an exception to be thrown

my $num3 = '42'.Int.self;
# Int type has a .self method, so here $num3 has `42` in it

(my $stuff = '♥'.Int).so;
say $stuff.self; # OUTPUT: «(HANDLED) Cannot convert string to number…»
# Here, Failure is handled, so .self just returns it as is
Code

method Bool§

multi method Bool(Failure:D: --> Bool:D)
Code

Returns False, and marks the failure as handled.

sub f() { fail };
my $v = f;
say $v.handled; # OUTPUT: «False␤»
say $v.Bool;    # OUTPUT: «False␤»
say $v.handled; # OUTPUT: «True␤»
Code

method Capture§

method Capture()
Code

Throws X::Cannot::Capture if the invocant is a type object or a handled Failure. Otherwise, throws the invocant's exception.

method defined§

multi method defined(Failure:D: --> Bool:D)
Code

Returns False (failures are officially undefined), and marks the failure as handled.

sub f() { fail };
my $v = f;
say $v.handled; # OUTPUT: «False␤»
say $v.defined; # OUTPUT: «False␤»
say $v.handled; # OUTPUT: «True␤»
Code

method list§

multi method list(Failure:D:)
Code

Marks the failure as handled and throws the invocant's exception.

sub fail§

multi fail(--> Nil)
multi fail(*@text)
multi fail(Exception:U $e  --> Nil )
multi fail($payload --> Nil)
multi fail(|cap (*@msg) --> Nil)
multi fail(Failure:U $f --> Nil)
multi fail(Failure:D $fail --> Nil)
Code

Exits the calling Routine and returns a Failure object wrapping the exception $e - or, for the cap or $payload form, an X::AdHoc exception constructed from the concatenation of @text. If the caller activated fatal exceptions via the pragma use fatal;, the exception is thrown instead of being returned as a Failure.

# A custom exception defined
class ForbiddenDirectory is Exception {
    has Str $.name;

    method message { "This directory is forbidden: '$!name'" }
}

sub copy-directory-tree ($dir) {
    # We don't allow for non-directories to be copied
    fail "$dir is not a directory" if !$dir.IO.d;
    # We don't allow 'foo' directory to be copied too
    fail ForbiddenDirectory.new(:name($dir)) if $dir eq 'foo';
    # or above can be written in method form as:
    # ForbiddenDirectory.new(:name($dir)).fail if $dir eq 'foo';
    # Do some actual copying here
    ...
}

# A Failure with X::AdHoc exception object is returned and
# assigned, so no throwing Would be thrown without an assignment
my $result = copy-directory-tree("cat.jpg");
say $result.exception; # OUTPUT: «cat.jpg is not a directory␤»

# A Failure with a custom Exception object is returned
$result = copy-directory-tree('foo');
say $result.exception; # OUTPUT: «This directory is forbidden: 'foo'␤»
Code

If it's called with a generic Failure, an ad-hoc undefined failure is thrown; if it's a defined Failure, it will be marked as unhandled.

sub re-fail {
    my $x = +"a";
    unless $x.defined {
        $x.handled = True;
        say "Something has failed in \$x ", $x.^name;
        # OUTPUT: «Something has failed in $x Failure␤»
        fail($x);
        return $x;
    }
}

my $x = re-fail;
say $x.handled; # OUTPUT: «False␤»
Code

Class relation diagram§

Rakudo Type Relation for Failure Failure Failure Nil Nil Failure->Nil is