class Failure
class Failure is Nil { }
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}."
}
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))
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ā¼ā¤Ā»
method handled
method handled(Failure:D: --> Bool:D) is rw
Returns True
for handled failures, False
otherwise.
sub f() { fail }; my $v = f; say $v.handled; # OUTPUT: Ā«Falseā¤Ā»
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ā¤Ā»
method exception
method exception(Failure:D: --> Exception)
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ā¤Ā»
method self
method self(Failure:D: --> Failure:D)
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
method Bool
multi method Bool(Failure:D: --> Bool:D)
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ā¤Ā»
method Capture
method Capture()
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)
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ā¤Ā»
method list
multi method list(Failure:D:)
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)
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'ā¤Ā»
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ā¤Ā»