Exceptions and Failures
How Raku throws, catches, resumes and reports an exception, how a Failure holds one back until somebody looks, and what $!, CATCH, CONTROL and the phasers see on the way.
Raku has two ways of saying that something went wrong. die throws an exception at once: control leaves block after block and routine after routine until a handler takes it, and when none does, the program ends. fail returns a Failure instead: an undefined value that carries an exception and throws it only if the value is used without being checked.
Handlers come in two forms. try catches everything and leaves the exception in the variable $!. The CATCH phaser, written inside a block, chooses the exceptions it wants with when. A second family, the control exceptions, is how warn, next, last, return and take travel; the CONTROL phaser sees those.
This chapter follows an exception from die to the report on standard error, and a Failure from fail to the moment it throws. What a Failure does when nobody uses it is in Values Nobody Uses; a Failure as an undefined value is in Nil, Any and the Undefined.
16.1 An uncaught exception prints a backtrace and exits with status 1
When no handler takes an exception, Rakudo writes its message to standard error, then one line for each routine or block that was active when it was thrown, innermost first, then an empty line. The program stops.
sub check($n) { die "negative: $n" if $n < 0; $n } say check(3); say check(-1); say "not reached";
3
negative: -1 in sub check at example.raku line 2 in block <unit> at example.raku line 6
The exit status is 1. A warning also goes to standard error, but the program goes on and the status stays 0. Running two small programs as child processes shows both:
my $p = run $*EXECUTABLE, "-e", 'die "oops"', :err; say $p.err.slurp.lines[0]; say $p.exitcode; my $q = run $*EXECUTABLE, "-e", 'warn "careful"', :err; say $q.exitcode;run it locally
oops 1 0
16.2 say $! prints the backtrace along with the message
say prints an object's .gist, and the gist of a thrown exception is the whole report: the message, the backtrace and an empty line. put, ~ and string interpolation use .Str, which is the message alone, as is .message.
sub load { die "config missing" } try load(); say $!; put $!; say "Error: $!"; say $!.message;
config missing in sub load at example.raku line 1 in block <unit> at example.raku line 2 config missing Error: config missing config missing
16.3 try gives its block's value, or Nil with the exception in $!
try takes a block or a single statement. When nothing is thrown it gives the value of what it ran; when something is thrown it gives Nil and puts the exception in $!. Inside a try a Failure throws as soon as a call returns it (see Values Nobody Uses), so try also catches the Failure of a conversion such as +"abc".
say try { 42 }; say (try { die "x" }).raku; say try 42; say (try die "y").raku; say $!.message; my $n = try +"abc"; say $n.raku, " ", $!.^name;
42 Nil 42 Nil y Any X::Str::Numeric
Assigning the Nil to $n leaves the variable holding Any.
16.4 A successful try resets $! to Any
Before any try has run, $! is Nil. A try that catches sets it to the exception, and a try that succeeds sets it back to Any, an undefined value. $! therefore always describes the most recent try, not the most recent failure.
say $!.raku; try { die "first" }; say $!.raku; try { 1 }; say $!.raku; say $!.defined;
Nil X::AdHoc.new(payload => "first") Any False
The editor’s engine, Raku++, prints something else here
Nil X::AdHoc.new(payload => "first") Nil False
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
The try that resets it may be anywhere in the same routine, even hidden in a nested block. An exception that must be kept belongs in a variable of its own before the next try runs:
try { die "saved" }; { try { 1 } } say $!.raku;
Any
The editor’s engine, Raku++, prints something else here
Nil
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.6 A nested try hands its exception to the rest of the outer block
An inner try sets the $! that the rest of the outer try block reads. A die with no argument there rethrows it. When the outer try then succeeds, it resets $!, and whatever the inner one caught is gone.
try { try { die "inner" }; die "outer after " ~ $!.message; } say $!.message; try { try { die "again" }; die; } say $!.message; try { try { die "discarded" }; 1 } say $!.raku;
outer after inner again Any
The editor’s engine, Raku++, prints something else here
outer after inner again Nil
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.7 CATCH handles what a when or default matches, then leaves the block
A CATCH block handles the exceptions thrown anywhere in the block that contains it, before or after the CATCH itself. Inside it the exception is the topic $_, and when clauses choose by type. Once a clause has run, the block that holds the CATCH is finished: the statements after the throw do not run, and execution continues after that block.
sub risky($n) { ... if $n == 1; X::NYI.new(feature => "level two").throw if $n == 2; die "plain failure"; } for 1..3 -> $n { risky($n); say "not reached"; CATCH { when X::StubCode { say "stub: ", .message } when X::NYI { say "missing: ", .feature } default { say "other: ", .message } } } say "done";
stub: Stub code executed missing: level two other: plain failure done
Each iteration of the for is one run of its block, so the loop goes on with the next element. A when smartmatches the exception: a type matches by class, a regex matches against the message, and a string must equal the message:
{
CATCH { when /disk/ { say "regex matched: ", .message } }
die "disk full";
}
{
CATCH { when "exact text" { say "string matched" } }
die "exact text";
}regex matched: disk full string matched
16.8 An exception that no when matches goes on outward
When no clause of a CATCH matches, the exception continues to the next handler out, as though the CATCH were not there. A CATCH with no when and no default runs its code and still matches nothing:
sub inner { die "unmatched"; CATCH { when X::StubCode { say "never" } } } sub middle { inner(); CATCH { say "middle's CATCH ran for: ", .message } } sub outer { middle(); CATCH { default { say "outer caught: ", .message } } } outer(); say "after";
middle's CATCH ran for: unmatched outer caught: unmatched after
16.9 A try with its own CATCH catches nothing else
A try catches every exception, but only through a handler of its own, and a CATCH in its block replaces that handler. Whatever the CATCH does not match leaves the try as though there were no try at all:
try { die "escapes"; CATCH { when X::StubCode { say "never" } } } say "not reached";
(nothing)escapes in block <unit> at example.raku line 2
The same goes for an exception thrown inside the CATCH. Only a second try around the first one catches it:
try { try { die "first"; CATCH { default { die "while handling: " ~ .message } } } say "not reached"; } say $!.message;
while handling: first
16.10 Inside CATCH the exception is $_, and it never becomes $!
A CATCH hands the exception over as $_ only. Inside the CATCH, $! is Nil, and $/ is Nil too. An exception that a CATCH handles is never stored in $! at all: after the block, $! still holds what the last try left there.
try { die "earlier" }; { die "now"; CATCH { default { say "topic: ", .message; say "inside: ", $!.raku; } } } say "after: ", $!.message;
topic: now inside: Nil after: earlier
The editor’s engine, Raku++, prints something else here
topic: now inside: X::AdHoc.new(payload => "now") after: earlier
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
A try whose own CATCH handles the exception leaves $! untouched in the same way.
16.11 A CATCH in a loop body ends one iteration, and $_ is the exception
A handled exception finishes the current run of the loop body, and the loop goes on. Inside the CATCH, $_ is the exception, not the loop's topic:
for 1..4 { CATCH { default { say "skipped $_" } } die "odd" if $_ %% 2; say $_; }
1 skipped odd 3 skipped odd
next, last and redo work from inside the CATCH and act on the loop. Name the loop variable, as with -> $n, to keep it in reach:
for 1..4 -> $n { CATCH { default { say "stop at $n"; last } } die "too big" if $n == 3; say $n; } my $attempts = 0; for <a b> -> $item { CATCH { default { say "retrying $item"; redo } } die "flaky" if $item eq "a" && $attempts++ == 0; say "done $item"; }
1 2 stop at 3 retrying a done a done b
16.12 A trailing CATCH makes the block's value Nil
A block's value is the value of its last statement, and a CATCH is a statement. Written last, it makes the block's value Nil even when nothing was thrown. Written first, it leaves the value alone:
sub compute { 42 } say (try { compute(); CATCH { default { say "failed" } } }).raku; say (try { CATCH { default { say "failed" } }; compute() }).raku; sub trailing { compute(); CATCH { default { say "failed" } } } say trailing().raku; sub leading { CATCH { default { say "failed" } }; compute() } say leading().raku;
Nil 42 Nil 42
The editor’s engine, Raku++, prints something else here
42 42 Nil 42
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
When the CATCH does handle something, the block's value is Nil wherever the CATCH stands. The value of the handler's own code is thrown away; it does not become the value of the block:
say (do { die "x"; CATCH { default { "handler value" } } }).raku; sub f { CATCH { default { "handler value" } }; die "x"; "unreached" } say f().raku;
Nil Nil
16.13 .resume continues after the statement that threw
Calling .resume on the exception inside a CATCH makes the throw return, and execution goes on with the next statement in the frame that threw. A routine that dies and is resumed therefore runs to its end and returns its own value, which overwrites anything the CATCH assigned:
{
say "one";
die "a problem";
say "two";
CATCH { default { say "handled: ", .message; .resume } }
}
sub bad { die "in bad"; "bad's own value" }
my $v = "init";
{
CATCH { default { $v = "from CATCH"; .resume } }
$v = bad();
}
say $v;one handled: a problem two bad's own value
The editor’s engine, Raku++, prints something else here
one handled: a problem two from CATCH
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
A resumed die returns Nil. A resumed failed assignment leaves the variable as it was. The block runs to its end and gives the value of its last statement:
my $r = do { CATCH { default { say "caught ", .^name; .resume } } say (die "x").raku; my Int $x = "text"; say "x is ", $x.raku; "the block's value" }; say $r;
caught X::AdHoc Nil caught X::TypeCheck::Assignment x is Int the block's value
The editor’s engine, Raku++, prints something else here
caught X::AdHoc caught X::TypeCheck::Assignment x is Int the block's value
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.14 .resume refuses a finished try and a missing method
An exception can be resumed only while its handler runs. Once the try has finished, .resume on the exception in $! throws "Too late". A call of a method the object does not have, X::Method::NotFound, cannot be resumed at all. Both refusals are X::AdHoc exceptions thrown inside the CATCH, so they escape the try that holds it (see A try with its own CATCH catches nothing else):
try { die "b" }; try $!.resume; say $!.message; try { try { 1.nosuch; CATCH { default { .resume } } } } say $!.message;
Too late to resume this exception This exception is not resumable
The editor’s engine, Raku++, prints something else here
Cannot resume without an active exception handler Nil
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
The exceptions of die, of a typed exception's .throw, of a failed type check, and the Failures that a try turns into throws all resume. The control exceptions have rules of their own, described below.
16.15 .rethrow keeps the original backtrace; .throw starts a new one
Both methods throw the same exception object again from inside a CATCH. .rethrow sends it on with the backtrace it already has. .throw records a new one where it is called, and because a CATCH runs inside the die that it handles, the new backtrace lists the handler's frames first and the original frames after them:
sub inner { die "deep" } sub keeps { inner(); CATCH { default { .rethrow } } } sub renews { inner(); CATCH { default { .throw } } } try keeps(); say $!.backtrace.list.map(*.subname).grep(*.chars); try renews(); say $!.backtrace.list.map(*.subname).grep(*.chars);
(throw die inner keeps <unit>) (throw throw die inner renews <unit>)
The editor’s engine, Raku++, prints something else here
(throw die inner keeps <unit>) (throw die inner renews <unit>)
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
The first names in each list are frames of Rakudo's own code. Uncaught, the exception from .throw reports the CATCH twice, as a block and as a frame named any:
sub inner { die "deep" } sub renews { inner(); CATCH { default { .throw } } } renews();
(nothing)deep in block at example.raku line 4 in any at example.raku line 4 in sub inner at example.raku line 1 in sub renews at example.raku line 3 in block <unit> at example.raku line 6
16.16 An exception class supplies its own message
A class that inherits from Exception becomes a throwable type. Its attributes are the details a handler can read, and a message method gives the text of the report:
class X::Empty is Exception { has $.what; method message { "$!what is empty" } } sub first-of(@list) { X::Empty.new(what => "the list").throw unless @list; @list[0] } try first-of([]); say $!.^name; say $!.message; say $!.what; first-of([]);
X::Empty the list is empty the list
the list is empty in sub first-of at example.raku line 6 in block <unit> at example.raku line 13
Without a message method, calling .message dies with "Stub code executed", and the object has fallback texts for its .Str and .gist. Uncaught, it reports Died with and the class name:
class E is Exception { } my $e = E.new; say $e.Str; say $e.gist; try $e.message; say $!.^name, ": ", $!.message; E.new.throw;
Something went wrong in (E) Unthrown E with no message X::AdHoc: Stub code executed
Died with E in block <unit> at example.raku line 7
The editor’s engine, Raku++, prints something else here
E.new Nil: Nil
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.17 An exception object has no backtrace until it is thrown
An exception can be built and kept like any object. Until it is thrown, its .backtrace is Nil, its gist is the plain message, and .resume refuses it with the message "Can only resume an exception object", although it is one. .throw needs an instance, not the class. .rethrow on an object that was never thrown throws it with a fresh backtrace, and the handler receives the very same object:
my $e = X::AdHoc.new(payload => "prepared"); say $e.backtrace.raku; say $e.gist; try $e.resume; say $!.message; try X::AdHoc.throw; say $!.^name; try $e.rethrow; say $! === $e, " ", $!.backtrace.^name;
Nil prepared Can only resume an exception object X::Parameter::InvalidConcreteness True Backtrace
The editor’s engine, Raku++, prints something else here
({:file("example.raku"), :line(2)},)
prepared
Cannot resume without an active exception handler
X::Parameter::InvalidConcreteness
True BacktraceMeasured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.18 die with anything but an Exception throws an X::AdHoc
die wraps a value that is not an exception in X::AdHoc. The value is kept unchanged as the .payload, and the message is its string form. Several arguments are joined with no separator, like the arguments of print:
try die 42; say $!.^name, " ", $!.payload.^name, " ", $!.payload + 1; try die [1, 2]; say $!.message, " ", $!.payload.^name; try die "a", "b", 3; say $!.message; class Temperature { has $.degrees; method Str { "$!degrees degrees" } } my $t = Temperature.new(degrees => 451); try die $t; say $!.message, " ", $!.payload.degrees;
X::AdHoc Int 43 1 2 Array ab3 451 degrees 451
.message is always a string, whatever the payload. An X::AdHoc built without a payload has the message "Unexplained error".
my $e = X::AdHoc.new(payload => 42); say $e.message.^name; say $e.payload.^name; say $e.raku; say X::AdHoc.new.message;
Str Int X::AdHoc.new(payload => 42) Unexplained error
16.19 die without an argument rethrows $!, or says "Died"
A bare die throws the exception in the current routine's $! if there is one, and an X::AdHoc with the message "Died" if there is not. A sub has a $! of its own (see Each routine has its own $!, shared by its blocks), so a bare die in a sub does not see the caller's. A type object is not an exception to throw: die reports it as undefined.
try die; say $!.message; try die "first"; try die; say $!.message; sub fresh { try die; $!.message } say fresh(); try die Exception; say $!.message; try die X::NYI; say $!.message;
Died first Died Died with undefined Exception Died with undefined X::NYI
The editor’s engine, Raku++, prints something else here
Died first Died
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
die Nil throws an X::AdHoc whose payload is Nil and whose message is empty, so the report begins with an empty line:
die Nil;
(nothing)in block <unit> at example.raku line 1
16.20 die throws an exception object as it is, and a Failure's exception
Given an exception object, die throws that object, attributes and all. Given a Failure, it throws the exception inside it, and the Failure stays unhandled. An exception among other arguments is only a piece of text: its message joins the rest in a new X::AdHoc.
try die X::NYI.new(feature => "time travel"); say $!.^name, ": ", $!.feature; sub parse { fail "cannot parse" } my $f = parse(); try die $f; say $!.^name, ": ", $!.message; say $f.handled; try die "prefix: ", X::NYI.new(feature => "teleport"); say $!.^name, ": ", $!.message;
X::NYI: time travel X::AdHoc: cannot parse False X::AdHoc: prefix: teleport not yet implemented. Sorry.
The editor’s engine, Raku++, prints something else here
X::NYI: time travel X::AdHoc: exception cannot parse message cannot parse False X::AdHoc: prefix: teleport not yet implemented. Sorry.
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.21 A trailing newline does not hide the backtrace; :without-backtrace does
In Perl, a message ending in a newline suppresses the location. In Raku the newline is part of the message, and the backtrace follows it as usual:
die "oops\n";
(nothing)oops in block <unit> at example.raku line 1
The named argument :without-backtrace makes an exception that has none. Uncaught, it prints the message alone, without even the closing empty line:
die :without-backtrace, "short and sweet";
(nothing)short and sweet
The quirk is its gist. The exception's .backtrace is Nil, and in its place .gist lists the frames active where .gist is called, which have nothing to do with the die:
sub thrower { die :without-backtrace, "wb" } try thrower(); sub show($e) { say $e.gist } show($!); say $!.without-backtrace;
wb in sub show at example.raku line 3 in block <unit> at example.raku line 4 True
The editor’s engine, Raku++, prints something else here
without-backtrace Truewb in sub thrower at example.raku line 1 in block <unit> at example.raku line 2
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.22 warn prints to standard error, goes on, and returns 0
warn reports its message on standard error, followed by one line saying where it was called, and then execution continues. It returns 0. The line names only the innermost frame, not the chain of calls that led there. note prints its arguments with no location at all and returns True.
sub check($x) { warn "odd value $x" if $x % 2; $x } my $r = check(3); say "still running with $r"; say (warn "again").raku; note "a note"; say (note "another").raku;
still running with 3 0 Bool::True
odd value 3 in sub check at example.raku line 2 again in block <unit> at example.raku line 7 a note another
The editor’s engine, Raku++, prints something else here
still running with 3 Bool::True Bool::True
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.23 warn joins its arguments, and an empty warning gets a stock message
Like die, warn joins several arguments with no separator, and an Array among them with spaces. With no arguments, or an empty string, the message is "Warning: something's wrong". A trailing newline stays in the message:
warn; warn ""; warn 42, "x", [1, 2]; warn "with newline\n"; say "end";
end
Warning: something's wrong in block <unit> at example.raku line 1 Warning: something's wrong in block <unit> at example.raku line 2 42x12 in block <unit> at example.raku line 3 with newline in block <unit> at example.raku line 4
16.24 CONTROL catches warnings; without .resume it leaves the block
A warning is a control exception of type CX::Warn, and a CATCH never sees it. A CONTROL block does. With .resume, warn returns and the block goes on. A CONTROL that matches the warning and does not resume ends the block, as a CATCH does:
my @log; { CONTROL { when CX::Warn { @log.push: .message; .resume } } warn "first"; warn "second"; say "the block went on"; } say @log; { CONTROL { when CX::Warn { say "caught ", .message } } warn "stops here"; say "not reached"; } say "after the block";
the block went on [first second] caught stops here after the block
The CX::Warn object has the full backtrace that the printed warning leaves out:
sub a { warn "deep warning" } sub b { a() } { CONTROL { when CX::Warn { say .backtrace.list.map(*.subname).grep(*.chars); .resume } } b(); }
(warn a b <unit>)
The editor’s engine, Raku++, prints something else here
(a b <unit>)
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.25 quietly swallows warnings before any CONTROL sees them
quietly in front of a block or a statement discards the warnings raised while it runs, warn and Rakudo's own warnings alike, and execution goes on. A CONTROL outside never hears of them:
{
CONTROL { when CX::Warn { say "outer saw it"; .resume } }
quietly { warn "hushed" }
quietly warn "also hushed";
say "quiet";
}
quietly say "value: " ~ Nil;quiet value:
Without quietly, the last line would warn about Nil in string context (see Nil, Any and the Undefined).
16.26 A last caught by CONTROL does not end the loop
next, last and redo are control exceptions too, CX::Next, CX::Last and CX::Redo, and a CONTROL in the loop body receives them. A handler that matches one and does nothing more swallows it: the body is left, and the loop goes on. .rethrow lets it have its effect:
for 1..4 { CONTROL { when CX::Last { say "last swallowed" } } last if $_ == 2; say $_; } for 1..4 { CONTROL { when CX::Last { say "last passed on"; .rethrow } } last if $_ == 2; say $_; }
1 last swallowed 3 4 1 last passed on
The editor’s engine, Raku++, prints something else here
1 1
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
A CONTROL with default catches every control exception, and its .message names it:
for 1..3 { CONTROL { default { say "control: ", .^name, " ", .message } } next if $_ == 1; last if $_ == 2; }
control: CX::Next <next control exception> control: CX::Last <last control exception>
The editor’s engine, Raku++, prints something else here
(nothing: Raku++ accepts the program and prints nothing)
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
The quirk is .resume: warnings resume, but these three refuse, and the refusal is an ordinary exception that leaves the loop:
try { for 1..3 { CONTROL { when CX::Next { .resume } } next; } } say $!.^name, ": ", $!.message;
X::AdHoc: This exception is not resumable
The editor’s engine, Raku++, prints something else here
Nil: Nil
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.27 Rethrowing a caught return, take or emit loses its value
A CONTROL also sees return (CX::Return), take (CX::Take) and emit (CX::Emit). The documentation describes .rethrow as throwing the exception again, and a rethrown CX::Next does go on to the next iteration. For these three, in Rakudo 2026.08, the construct receives the control exception itself instead of the value: the routine returns the CX::Return object, and gather collects CX::Take objects. A CONTROL that does not catch the return leaves its value alone.
sub five { CONTROL { when CX::Return { say "seen: ", .message; .rethrow } } return 5; } say five().raku; my @g = gather { CONTROL { when CX::Take { .rethrow } } take 1; take 2; } say @g.raku;
seen: <return control exception> CX::Return.new [CX::Take.new, CX::Take.new]
The editor’s engine, Raku++, prints something else here
5 [1, 2]
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
A rethrown emit delivers the CX::Emit object, and say prints its gist, message and backtrace:
my $s = supply { CONTROL { when CX::Emit { .rethrow } } emit 7; } react { whenever $s { say "got: ", $_ } }
got: <emit control exception> in block <unit> at example.raku line 3
The editor’s engine, Raku++, prints something else here
got: 7
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
Resuming instead is no better: a resumed take or emit is skipped, so nothing is gathered or emitted, and a resumed return is refused as not resumable. A CONTROL also sees a succeed written outside a when, but not the succeed or proceed that a when block performs.
16.28 Loop control outside a loop throws X::ControlFlow
last, next and redo with no loop around them at run time, take outside gather, emit outside a supply and succeed outside a when throw X::ControlFlow. Its .illegal names the keyword and .enclosing the construct that was missing. return outside a routine throws the subclass X::ControlFlow::Return. These are ordinary exceptions, caught by try and CATCH:
sub stop { last } try stop(); say $!.^name, ": ", $!.illegal, " / ", $!.enclosing; say $!.message; try take 1; say $!.illegal, " / ", $!.enclosing; try emit 1; say $!.illegal, " / ", $!.enclosing; try succeed; say $!.illegal, " / ", $!.enclosing; try { return 1 }; say $!.^name, ": ", $!.illegal, " / ", $!.enclosing; say $! ~~ X::Control;
X::ControlFlow: last / loop construct last without loop construct take / gather emit / supply or react succeed / when clause X::ControlFlow::Return: return / Routine False
The editor’s engine, Raku++, prints something else here
(nothing on standard output; standard error says:) last without loop construct
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
A return inside a gather has the same problem even when the gather is written inside a sub. The gather block runs lazily, when its values are wanted, and by then the sub it was written in has already returned:
sub evens(@n) { gather for @n { return "odd found" if $_ % 2; take $_ } } my @e = evens([2, 4]); say @e; try { my @f = evens([2, 3]) }; say $!.^name;
[2 4] X::ControlFlow::Return
16.29 An uncaught stray last is reported like a compile-time error
A last, next or redo that finds no loop is detected while the program runs, as the output of the first line shows. Its report nevertheless starts with the ===SORRY!=== header of a compile-time error, and it has no backtrace and no closing empty line:
say "before"; last;
before
===SORRY!=== last without loop construct
Its gist, caught, is the message and an empty line. A take or a return in the wrong place is reported in the usual way, with a backtrace.
16.30 next and last in a called sub act on the caller's loop
Loop control is found dynamically, by walking out through the calls, not by looking at the code around the keyword. A last in a sub ends whatever loop is running when the sub is called:
sub stop { last } for 1..5 { stop() if $_ == 3; say $_; } sub skip-even($n) { next if $n %% 2 } for 1..5 { skip-even($_); say "odd $_"; }
1 2 odd 1 odd 3 odd 5
16.31 A class that does X::Control reaches CONTROL and never CATCH
A user class can be a control exception by doing the role X::Control. Thrown, it passes every CATCH and goes to the nearest CONTROL, which can resume it: a progress report, for instance, that the caller may listen to or not.
class CX::Progress does X::Control { has $.done; method message { "$!done% done" } } sub work { for 25, 50, 100 { CX::Progress.new(done => $_).throw } "finished" } { CATCH { default { say "CATCH: never" } } CONTROL { when CX::Progress { say .message; .resume } } say work(); }
25% done 50% done 100% done finished
A control exception that no CONTROL handles turns into an X::ControlFlow with .illegal "control exception" and .enclosing "handler", which a try does catch. Throwing a CX:: object by hand is therefore not the same as the keyword: CX::Last.new.throw in a loop does not end it but leaves it with that error, and a hand-made CX::Warn is no warning without a CONTROL.
class CX::Progress does X::Control { method message { "progress" } } try CX::Progress.new.throw; say $!.^name, ": ", $!.illegal, " / ", $!.enclosing; try { for 1..3 { say $_; CX::Last.new.throw } } say $!.^name, ": ", $!.illegal; CX::Warn.new(message => "hand-made").throw; say "not reached";
X::ControlFlow: control exception / handler 1 X::ControlFlow: control exception
control exception without handler in block <unit> at example.raku line 8
The editor’s engine, Raku++, prints something else here
(nothing on standard output; standard error says:)
No such method 'illegal' for invocant of type 'CX::Progress'
(X::Method::NotFound)
in block <unit> at example.raku line 5
5 | say $!.^name, ": ", $!.illegal, " / ", $!.enclosing;Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.32 A bare succeed yields an internal null that cannot be printed
succeed leaves the when or given block early, and its argument becomes the block's value. A do given yields that value, a list stays a List, and proceed moves on to the next when. What a given yields when nothing matches is in Values Nobody Uses.
say (do given 5 { when Int { succeed "early"; "not reached" } }).raku; say (do given 5 { when Int { succeed 1, 2 } }).raku; say (do given 5 { when Int { proceed }; when 5 { "five" } }).raku; say (do given 5 { when Int { } }).raku;
"early" (1, 2) "five" Nil
The editor’s engine, Raku++, prints something else here
"early" [1, 2] "five" Nil
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
An empty when yields Nil. A bare succeed, in Rakudo 2026.08, yields the virtual machine's internal null value, VMNull, which is not a Raku object and has no methods at all, not even the .gist that say calls:
my $v = do given 5 { when Int { succeed } }; say $v;
(nothing)No such method 'gist' for invocant of type 'VMNull'. Found 'gist' on type 'Mu' in block <unit> at example.raku line 2
The editor’s engine, Raku++, prints something else here
(Any)
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.33 fail returns a Failure from the routine; die throws
fail leaves the routine it is in, as return does, and returns a Failure that wraps the exception. The caller gets a value it can test: a Failure is undefined and false. Testing it marks it handled; an unhandled Failure throws when it is used as a value or sunk (see Values Nobody Uses).
sub parse-die($s) { die "bad input: $s" } sub parse-fail($s) { fail "bad input: $s" } my $a = try parse-die("x"); say "die: ", $a.raku, ", ", $!.message; my $b = parse-fail("y"); say "fail: ", $b.^name, ", ", $b.exception.message; say $b.defined;
die: Any, bad input: x fail: Failure, bad input: y False
16.34 fail accepts what die accepts
fail wraps its arguments exactly as die does: a plain value or several joined into an X::AdHoc, an exception object kept as it is. A bare fail wraps the $! it finds (see A bare fail and Failure.new look for $! in different places) or makes the message "Failed", and a type object gives "Failed with undefined". Failure.new takes the same arguments.
sub f(|c) { fail |c } sub show($f) { $f.so; say $f.exception.^name, ": ", $f.exception.message } show f("text"); show f(42); show f("a", "b", 3); show f(X::NYI.new(feature => "that")); show f(); show f(X::NYI); show Failure.new; show Failure.new("given to new");
X::AdHoc: text X::AdHoc: 42 X::AdHoc: ab3 X::NYI: that not yet implemented. Sorry. X::AdHoc: Failed X::AdHoc: Failed with undefined X::NYI X::AdHoc: Failed X::AdHoc: given to new
The editor’s engine, Raku++, prints something else here
X::AdHoc: text X::AdHoc: 42 X::AdHoc: a X::NYI: that not yet implemented. Sorry. X::AdHoc: Failed X::AdHoc: X::AdHoc: Failed X::AdHoc: given to new
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
An exception object can also fail itself, and .Failure wraps it without leaving the routine. Both Failures start unhandled:
my $e = X::AdHoc.new(payload => 42); sub f { $e.fail; "not reached" } my $r = f(); say $r.^name, " ", $r.exception === $e, " ", $r.handled; my $g = $e.Failure; say $g.^name, " ", $g.handled; $r.so; $g.so;
Failure True False Failure False
The editor’s engine, Raku++, prints something else here
(nothing on standard output; standard error says:)
X::AdHoc
in sub f at example.raku line 2
2 | sub f { $e.fail; "not reached" }
in block <unit> at example.raku line 3Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.35 Outside a routine, fail throws like die
A fail in a block inside a routine returns from the routine, not from the block. With no routine around it, in the mainline or in a try or do block there, fail has nothing to return from and throws at once:
sub first-word($s) { my $w = do { fail "empty" unless $s; $s.words[0] }; "word: $w"; } say first-word("hello world"); say first-word("").^name; my $v = try { fail "in try" }; say $v.raku, " ", $!.message; my $w = do { fail "in a do block" }; say "not reached";
word: hello Failure Any in try
in a do block in block <unit> at example.raku line 9
16.36 A bare fail and Failure.new look for $! in different places
Without arguments, both fail and Failure.new wrap the exception in $!, so a routine can pass on what its try caught. They look for it in different places. fail finds the routine's $! from any block inside the routine. Failure.new finds it only when called directly in the routine body; from a nested block it sees nothing and makes "Failed":
sub with-fail { try die "the cause"; fail; } sub fail-in-do { try die "the cause"; my $x = do { fail }; "not reached"; } sub with-new { try die "the cause"; Failure.new; } sub new-in-do { try die "the cause"; do { Failure.new }; } for &with-fail, &fail-in-do, &with-new, &new-in-do -> &c { my $f = c(); $f.so; say &c.name, ": ", $f.exception.message; }
with-fail: the cause fail-in-do: the cause with-new: the cause new-in-do: Failed
The editor’s engine, Raku++, prints something else here
with-fail: the cause fail-in-do: the cause with-new: the cause new-in-do: the cause
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.37 fail with a handled Failure re-arms it
A routine that receives a Failure, tests it, and fails with it passes on the same Failure object, unhandled again, so that its own caller is not let off. .handled can also be assigned to directly, in both directions.
sub lookup { fail "not found" } sub wrapper { my $r = lookup(); say "wrapper saw: ", $r ?? "true" !! "false"; fail $r; } my $w = wrapper(); say $w.handled; say $w.exception.message; $w.handled = True;
wrapper saw: false False not found
16.38 A Failure that throws far from its fail reports both places
A Failure keeps the backtrace of the place it was made. When it throws somewhere else, the report gives that backtrace first and then, under "Actually thrown at", the place where it finally went off:
sub lookup { fail "not found" } my $f = lookup(); say "stored"; sink $f;
stored
not found in sub lookup at example.raku line 1 in block <unit> at example.raku line 2 Actually thrown at: in block <unit> at example.raku line 4
16.39 An unhandled Failure can warn when the garbage collector frees it
A Failure that is never handled, never thrown and then dropped is a problem nobody saw. When the garbage collector frees such a Failure, Rakudo prints a warning with its message. The warning comes whenever the collector happens to run, so a program may print it on one run and not on the next, and a short program usually ends before it happens at all. Creating many Failures makes it certain:
sub lookup { fail "x" } for ^20000 { my $f = lookup(); $f.^name }not run
WARNING: unhandled Failure detected in DESTROY. If you meant to ignore it, you can mark it as handled by calling .Bool, .so, .not, or .defined methods. The Failure was: x
The warning repeats for each Failure collected. Testing the Failure, as the message suggests, keeps it quiet.
16.40 An unhandled Failure passes through subscripts, lists and for
Most uses of an unhandled Failure throw its exception. A few pass the Failure along untouched and do not mark it: a subscript returns the Failure itself, assigning it to an array makes one element, and for runs once with it. .DEFINITE is True, because a Failure is an object, not a type.
sub lookup { fail "not found" } my $f = lookup(); say $f[0].^name; say $f<key>.^name; my @a = $f; say @a.elems; for $f { say "the loop ran once" } say $f.DEFINITE; say $f.handled; say (try $f.elems) // "elems: threw";
Failure Failure 1 the loop ran once True False elems: threw
The editor’s engine, Raku++, prints something else here
Failure Failure 1 the loop ran once False False elems: threw
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
&& marks the Failure handled and returns it; andthen gives Empty. Once handled, a Failure is NaN in arithmetic and its .Int is the Int type object (see Values Nobody Uses), .Set makes a set of one element, and .Capture and .elems still throw:
sub lookup { fail "not found" } my $f = lookup(); say ($f && "never").^name; say $f.handled; my $g = lookup(); say ($g andthen "never").raku; my $h = lookup(); $h.so; say $h.Set.elems; say (try $h.Capture) // $!.^name; say (try $h.elems) // "elems still throws";
Failure True Empty 1 X::Cannot::Capture elems still throws
The editor’s engine, Raku++, prints something else here
Failure True Empty
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.41 A Failure owns the backtrace of its fail
The backtrace of a Failure belongs to the Failure, and the exception inside has none, because it was never thrown. .raku shows whether the Failure has been handled: a handled one prints as an orelse expression, which EVALs back to a handled Failure.
sub inner { fail "deep" } sub outer { inner() } my $f = outer(); say $f.backtrace.list.map(*.subname).grep(*.chars); say $f.exception.backtrace.raku; say $f.raku; $f.so; say $f.raku; say $f.raku.EVAL.handled;
(inner outer <unit>) Nil Failure.new(exception => X::AdHoc.new(payload => "deep"), backtrace => Backtrace.new) &CORE::infix:<orelse>(Failure.new(exception => X::AdHoc.new(payload => "deep"), backtrace => Backtrace.new), *.self) True
The editor’s engine, Raku++, prints something else here
(nothing on standard output; standard error says:)
deep
in block <unit> at example.raku line 4
4 | say $f.backtrace.list.map(*.subname).grep(*.chars);Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
Calling .new on a Failure object, rather than on the class, throws its exception and marks it handled.
16.42 use fatal makes a returned Failure throw at once
use fatal in a block turns every Failure that a call returns there into a throw, as a try does for its own block. A boolean test in the same expression still gets there first, so // supplies its fallback. no fatal switches it off again, even inside a try.
sub lookup { fail "not found" } { my $plain = lookup(); say "plain: ", $plain.^name; $plain.so; } { use fatal; my $x = lookup() // "fallback"; say "fatal with //: $x"; my $y = lookup(); say "not reached"; CATCH { default { say "fatal threw: ", .message } } } try { no fatal; my $z = lookup(); say "no fatal: ", $z.^name; $z.so; }
plain: Failure fatal with //: fallback fatal threw: not found no fatal: Failure
16.43 A Failure assigned to a typed variable reports two errors
A variable declared my Int cannot hold a Failure. Assigning one throws X::TypeCheck::Assignment, and its report names both problems, the Failure's own error first:
sub lookup { fail "not found" } my Int $n = lookup(); say "not reached";
(nothing)Earlier failure: not found in sub lookup at example.raku line 1 in block <unit> at example.raku line 2 Final error: Type check failed in assignment to $n; expected Int but got Failure (Failure.new(exceptio...) in block <unit> at example.raku line 2
Inside a try the Failure throws before the assignment is attempted, so what is caught there is the Failure's own exception:
sub lookup { fail "not found" } { my Int $n = lookup(); CATCH { default { say .^name } } } try { my Int $m = lookup() } say $!.^name;
X::TypeCheck::Assignment X::AdHoc
The editor’s engine, Raku++, prints something else here
X::AdHoc X::AdHoc
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.44 A backtrace holds every frame; its string shows the interesting ones
.backtrace returns a Backtrace, a list of Backtrace::Frame objects, innermost first. Each frame has a .subname (empty for an anonymous block), a .subtype, a .file, a .line, and flags such as .is-setting for frames in Rakudo's own code:
sub inner { die "bt" } sub outer { inner() } try { outer() } my $bt = $!.backtrace; say $bt.gist; for $bt.list -> $frame { say $frame.subname.raku, " ", $frame.subtype, $frame.is-setting ?? " (setting)" !! ""; }
Backtrace(6 frames) "throw" method (setting) "die" sub (setting) "inner" sub "outer" sub "" block "<unit>" block
The editor’s engine, Raku++, prints something else here
Backtrace(6 frames)
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
The string views choose among the frames. .Str, the view an uncaught exception prints, leaves out the setting's frames, and it folds some anonymous blocks into the frame that follows them, as it does here with the block of the try. .concise keeps only the routines outside the setting. .full and .summary include the setting's frames; here both have all six lines.
sub inner { die "bt" } sub outer { inner() } try { outer() } my $bt = $!.backtrace; print $bt.Str; say "--"; print $bt.concise; say "--"; say $bt.full.lines.elems; say $bt.summary.lines.elems;
in sub inner at example.raku line 1 in sub outer at example.raku line 2 in block <unit> at example.raku line 3 -- in sub inner at example.raku line 1 in sub outer at example.raku line 2 -- 6 6
The editor’s engine, Raku++, prints something else here
in sub inner at example.raku line 1 in sub outer at example.raku line 2 in block at example.raku line 3 in block <unit> at example.raku line 3 -- in sub inner at example.raku line 1 in sub outer at example.raku line 2 -- 4 6
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.45 .nice(:oneline) shows the second frame, not the first
.Str is .nice, and the documentation says .nice(:oneline) stops after the first frame. Here that is inner, where the exception was thrown; Rakudo 2026.08 gives the line after it:
sub inner { die "bt" } sub outer { inner() } try { outer() } print $!.backtrace.nice(:oneline);
in sub outer at example.raku line 2
The editor’s engine, Raku++, prints something else here
in sub inner at example.raku line 1 in sub outer at example.raku line 2 in block at example.raku line 3 in block <unit> at example.raku line 3
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.46 Backtrace.new records where it is created
A backtrace can be made without an exception. Backtrace.new lists the frames active where it is called, starting with its own new in the setting, and Backtrace.new(N) skips the first N frames. Every throw records a new backtrace, even of the same exception object, unless .throw is given one to use:
sub where-am-i { Backtrace.new } sub caller { where-am-i() } my $bt = caller(); say $bt.list.map(*.subname); print $bt.Str; sub skip-one { Backtrace.new(1) } say skip-one().list.map(*.subname); my $e = X::AdHoc.new(payload => "again"); try $e.throw; my $first = $e.backtrace; try $e.throw; say $e.backtrace === $first; try $e.throw($first); say $e.backtrace === $first;
(new where-am-i caller <unit>) in sub where-am-i at example.raku line 1 in sub caller at example.raku line 2 in block <unit> at example.raku line 3 (skip-one <unit>) False True
The editor’s engine, Raku++, prints something else here
(where-am-i caller <unit>) in sub where-am-i at example.raku line 1 in sub caller at example.raku line 2 in block <unit> at example.raku line 3 (<unit>) True True
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.48 await rethrows a thread's exception with a role mixed in
An exception inside start breaks its Promise, and await throws it again in the awaiting thread. The object that arrives is the original exception with the role X::Await::Died mixed in, so a when X::AdHoc still matches it. A fail in the start block breaks the Promise too.
my $p = start { die "in the thread" }; try await $p; say $!.^name; say $! ~~ X::AdHoc; say $!.message; say $p.status; say $p.cause.^name; my $q = start { fail "failed in the thread" }; try await $q; say $q.status, " ", $!.message;run it locally
X::AdHoc+{X::Await::Died}
True
in the thread
Broken
X::AdHoc
Broken failed in the threadUncaught, the report gives the place of the await first and the thread's exception after it:
my $p = start { die "in the thread" }; await $p;run it locally
(nothing)An operation first awaited:
in block <unit> at example.raku line 2
Died with the exception:
in the thread
in block at example.raku line 1
Promises are the subject of Promises, Locks and Awaiting.
16.49 A die in a map block fires when that element is computed
map is lazy: building the Seq runs nothing, and the block runs for each element when that element is asked for. The exception comes out wherever that happens, which may be far from the map:
my \seq = (1, 2, 3).map({ die "bad $_" if $_ == 2; $_ * 10 }); say "the map is built"; try { for seq { say $_ } } say "caught: ", $!.message;
the map is built 10 caught: bad 2
The editor’s engine, Raku++, prints something else here
(nothing on standard output; standard error says:)
bad 2
in block at example.raku line 1
1 | my \seq = (1, 2, 3).map({ die "bad $_" if $_ == 2; $_ * 10 });
in block <unit> at example.raku line 1Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.50 LEAVE runs on every exit; KEEP and UNDO split success from failure
LEAVE runs whenever its block is left, by an exception too. KEEP runs only when the block succeeds, and UNDO only when it does not. The queue runs in reverse order of declaration.
sub attempt($ok) { LEAVE say " LEAVE"; KEEP say " KEEP"; UNDO say " UNDO"; die "failed" unless $ok; "done" } say "success:"; attempt(True); say "exception:"; try attempt(False);
success: KEEP LEAVE exception: UNDO LEAVE
Success means a defined value: a block that ends with 0 succeeds, and one that ends with Nil, a type object or a Failure has failed. let follows the same rule (see Containers and Binding).
sub result($v) { KEEP say "KEEP"; UNDO say "UNDO"; $v } print "0: "; result(0); print "Nil: "; result(Nil); print "Int: "; result(Int); sub failing { UNDO say "UNDO"; fail "no" } print "fail: "; failing().so;
0: KEEP Nil: UNDO Int: UNDO fail: UNDO
16.51 A dying ENTER stops the queue; a dying LEAVE does not
An exception in an ENTER stops the ENTER phasers that would follow it and the block itself, but the LEAVE phasers still run. An exception in a LEAVE lets the other LEAVE phasers run and is thrown when they are done:
try { ENTER { say "ENTER 1"; die "in ENTER" } ENTER say "ENTER 2"; LEAVE say "LEAVE still runs"; say "body"; } say $!.message; try { LEAVE say "LEAVE a"; LEAVE { say "LEAVE b"; die "in LEAVE b" } 1 } say $!.message;
ENTER 1 LEAVE still runs in ENTER LEAVE b LEAVE a in LEAVE b
A LEAVE that dies while another exception is on its way replaces that exception. Two dying LEAVEs give one X::PhaserExceptions, which holds both. A CATCH in the block runs before its LEAVE phasers:
try { LEAVE die "from LEAVE"; die "original"; } say $!.message; try { LEAVE die "first LEAVE"; LEAVE die "second LEAVE"; 1 } say $!.^name; say $!.exceptions.map(*.message).sort; { LEAVE say "LEAVE"; CATCH { default { say "CATCH" } } die "x"; }
from LEAVE X::PhaserExceptions (first LEAVE second LEAVE) CATCH LEAVE
16.52 A LEAVE sees the exception in $! only inside the try
A LEAVE reads $! like any other code: the $! of its routine. When the LEAVE is in the block of the try itself, the try has already stored the exception there, and the LEAVE sees it. In a sub that dies, the LEAVE sees the sub's own $!, which nothing has set; in a block whose CATCH handles the exception, it sees whatever $! held before.
try { LEAVE say "LEAVE sees: ", $!.message; die "unwinding"; } sub s { LEAVE say "sub LEAVE sees: ", $!.raku; die "in sub"; } try s(); { LEAVE say "block LEAVE sees: ", $!.message; CATCH { default { say "handled" } } die "caught"; }
LEAVE sees: unwinding sub LEAVE sees: Nil handled block LEAVE sees: in sub
The editor’s engine, Raku++, prints something else here
LEAVE sees: unwinding sub LEAVE sees: X::AdHoc.new(payload => "in sub") handled block LEAVE sees: caught
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.53 In a loop, LEAVE runs before NEXT
NEXT runs when a loop body is about to go round again. The documentation says it runs before the body's LEAVE, and Roast asserts that order (S04-phasers/next.t), with a #?rakudo todo on the test. In Rakudo 2026.08 LEAVE comes first. LAST runs after the final LEAVE, as the documentation says.
for 1..2 { NEXT say "NEXT $_"; LEAVE say "LEAVE $_"; LAST say "LAST $_"; say "body $_"; }
body 1 LEAVE 1 NEXT 1 body 2 LEAVE 2 NEXT 2 LAST 2
16.54 PRE and POST throw X::Phaser::PrePost with the condition's source
A PRE phaser is a condition checked on entry to the block, a POST one checked on the way out, with the block's value in $_. A false condition throws X::Phaser::PrePost; its .phaser is PRE or POST and its .condition the source text of the condition.
sub half(Int $n) { PRE { $n %% 2 } POST { $_ < 100 } $n div 2 } say half(10); try half(7); say $!.^name, ": ", $!.phaser, " ", $!.condition.raku; say $!.message; try half(300); say $!.message;
5
X::Phaser::PrePost: PRE "\{ \$n \%\% 2 }"
Precondition '{ $n %% 2 }' failed
Postcondition '{ $_ < 100 }' failedThe editor’s engine, Raku++, prints something else here
5 X::Phaser::PrePost: PRE "False" Precondition 'False' failed Postcondition 'False' failed
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
The condition may also be written without a block:
sub root($x) { PRE $x >= 0; $x.sqrt } root(-4);
(nothing)Precondition '$x >= 0' failed in sub root at example.raku line 2 in block <unit> at example.raku line 5
A failing PRE runs nothing else of the block, not even ENTER or LEAVE, and the block's own CATCH does not see its exception. POST phasers run after LEAVE, in reverse order, and the first false one stops the rest:
sub guarded($x) { PRE { $x > 0 } CATCH { default { say "the sub's own CATCH" } } ENTER say "ENTER"; LEAVE say "LEAVE"; $x } try guarded(-1); say "outside: ", $!.^name; sub f { POST { say "POST 1"; True } POST { say "POST 2"; False } LEAVE say "LEAVE"; 42 } try f(); say $!.message;
outside: X::Phaser::PrePost
LEAVE
POST 2
Postcondition '{ say "POST 2"; False }' failedThe editor’s engine, Raku++, prints something else here
the sub's own CATCH LEAVE outside: Nil LEAVE POST 2 Postcondition 'False' failed
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.55 Compile-time errors are exceptions, and EVAL makes them catchable
A program that does not compile never runs, so nothing in it can catch its own compile-time error. Code compiled at run time with EVAL is different: its compile-time errors are exceptions like any other, of types under X::Comp, with .is-compile-time true and .line counting from the start of the EVAL'd string. An error raised while the EVAL'd code runs is an ordinary run-time exception.
use MONKEY-SEE-NO-EVAL; try EVAL 'say "fine";' ~ "\n\n" ~ '$undeclared'; say $!.^name; say $!.is-compile-time; say $!.line; say $! ~~ X::Comp; try EVAL '1 +'; say $!.^name; try EVAL 'die "at run time"'; say $!.^name, " ", $!.is-compile-time;
X::Undeclared 1 3 True X::Comp::AdHoc X::AdHoc False
The editor’s engine, Raku++, prints something else here
X::Undeclared
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
The EVAL'd say never runs. .is-compile-time answers 1 rather than True for some types. Each kind of compile-time error has a type of its own:
use MONKEY-SEE-NO-EVAL; for '1 <=> 2 <=> 3', 'my $0', 'CATCH { }; CATCH { }', 'BEGIN { die "early" }' -> $code { try EVAL $code; say $!.^name; }
X::Syntax::NonAssociative X::Syntax::Variable::Numeric X::Phaser::Multiple X::Comp::BeginTime
16.56 A call that can never match its signature fails at compile time
When a sub is called with a literal of a type its signature cannot accept, the compiler already knows that the call will fail, and it refuses the program. The line before the call never runs:
sub greet(Str $name) { say "hello $name" } say "start"; greet(42);
(nothing)===SORRY!=== Error while compiling example.raku Calling greet(Int) will never work with declared signature (Str $name) at example.raku:3 ------> <BOL><HERE>greet(42);
The editor’s engine, Raku++, prints something else here
start
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
The same value in a variable is checked when the call is made, and fails with a run-time X::TypeCheck::Binding::Parameter that try can catch:
sub greet(Str $name) { say "hello $name" } say "start"; my $n = 42; try greet($n); say $!.^name;
start X::TypeCheck::Binding::Parameter
Under EVAL, the compile-time form is an X::TypeCheck::Argument with the role X::Comp mixed in.
16.57 A compile-time exception carries the details of the complaint
The compile-time exception types have attributes for what the compiler found. X::Undeclared names the symbol and suggests similar names in scope. X::Comp::BeginTime holds the exception that a BEGIN block died with. A call that can never work lists the argument types and the signatures. Several problems found together arrive as one X::Comp::Group:
use MONKEY-SEE-NO-EVAL; try EVAL 'my $total = 1; say $totl'; say $!.what, " ", $!.symbol, " ", $!.suggestions.raku; try EVAL 'BEGIN { die "early" }'; say $!.exception.message, " / ", $!.use-case; try EVAL 'sub f(Str) { }; f 42'; say $!.^name, " ", $!.objname, " ", $!.arguments.raku, " ", $!.signature.raku; try EVAL 'class Stub { ... }'; say $!.^name, " ", $!.packages.raku; try EVAL 'for 1, 2, 3, { say 3 }'; say $!.^name, ": ", $!.sorrows.map(*.^name), ", ", $!.panic.^name;
Variable $totl ["\$total"]
early / evaluating a BEGIN
X::TypeCheck::Argument+{X::Comp} f ["Int"] ("(Str)",)
X::Package::Stubbed ["Stub"]
X::Comp::Group: (X::Syntax::BlockGobbled), X::Syntax::MissingThe editor’s engine, Raku++, prints something else here
Variable $totl ("\$total",)
early / evaluating a BEGIN
X::TypeCheck::Argument f ("Int",) "(Str)"
X::Package::Stubbed ("Stub",)
X::Comp::Group: (X::Syntax::BlockGobbled), X::Syntax::MissingMeasured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.58 Dispatch and type-check errors name what they got and expected
A failed method call is an X::Method::NotFound, with the method name, the type, and suggestions picked by spelling alone. A failed type check tells what arrived, what was expected, and which operation checked: binding for a parameter, assignment for a variable, returning for a return type. A failed where clause sets .constraint:
try 42.nosuch; say $!.^name, ": ", $!.method, " on ", $!.typename, ", suggestions ", $!.suggestions.raku; my $s = "text"; sub f(Int $x) { } try f($s); say $!.^name, ": ", $!.got.raku, " ", $!.expected.^name, " ", $!.parameter.name, " ", $!.operation; sub g($x where * > 5) { } my $one = 1; try g($one); say $!.constraint; try { my Int $x = $s }; say $!.^name, ": ", $!.symbol, " ", $!.operation; sub h(--> Str) { 5 } try h(); say $!.^name, ": ", $!.got, " ", $!.operation;
X::Method::NotFound: nosuch on Int, suggestions ["cosech"] X::TypeCheck::Binding::Parameter: "text" Int $x binding True X::TypeCheck::Assignment: $x assignment X::TypeCheck::Return: 5 returning
The editor’s engine, Raku++, prints something else here
(nothing on standard output; standard error says:)
No such method 'suggestions' for invocant of type 'X::Method::NotFound'
(X::Method::NotFound)
in block <unit> at example.raku line 2
2 | say $!.^name, ": ", $!.method, " on ", $!.typename, ", suggestions ", $!.suggestions.raku;Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
A multi with no matching candidate throws X::Multi::NoMatch with the proto in .dispatcher and the arguments in .capture. The wrong number of arguments, found at run time, is only an X::AdHoc. A value passed to an is rw parameter is an X::Parameter::RW:
proto area(|) {*} multi area(Int $side) { $side ** 2 } my $s = "big"; try area($s); say $!.^name, ": ", $!.dispatcher.name, " ", $!.capture.raku; sub two($a, $b) { } my @one = 1; try two(|@one); say $!.^name, ": ", $!.message; sub w($x is rw) { } try w(5 + 0); say $!.^name, ": ", $!.got, " ", $!.symbol;
X::Multi::NoMatch: area \("big")
X::AdHoc: Too few positionals passed; expected 2 arguments but got 1
X::Parameter::RW: 5 $xThe editor’s engine, Raku++, prints something else here
(nothing on standard output; standard error says:)
No such method 'dispatcher' for invocant of type 'X::Multi::NoMatch'
(X::Method::NotFound)
in block <unit> at example.raku line 5
5 | say $!.^name, ": ", $!.dispatcher.name, " ", $!.capture.raku;Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
16.59 Each misuse of a value has its own exception type
The built-in types report misuse with specific exception classes, and each class carries attributes for the details. Inside a try, the operations that return a Failure throw at once, so the exception can be read from $!:
try { 1.0 = 3 }; say $!.^name, ": ", $!.value, " ", $!.typename; try { my $l := (1, 2); $l.push(3) }; say $!.^name, ": ", $!.method, " ", $!.typename; try Date.new("2012-02-30"); say $!.^name, ": ", $!.what, " ", $!.got, " ", $!.range; try "foo"[2].self; say $!.^name, ": ", $!.what, " ", $!.got, " ", $!.range; try +"5 foo"; say $!.^name, ": ", $!.source, " at ", $!.pos, ", ", $!.reason; try (1+2i).Num; say $!.^name, ": ", $!.reason; try (1..*).elems.self; say $!.^name, ": ", $!.action; try { my @a; @a.pop.self }; say $!.^name, ": ", $!.action, " ", $!.what; try 1 div 0; say $!.^name, ": ", $!.using, " ", $!.numerator; try Mu.new(1); say $!.^name, ": ", $!.type.^name; try { my %h = 1 }; say $!.^name, ": ", $!.found; try !!! "unfinished"; say $!.^name, ": ", $!.message;
X::Assignment::RO: 1 Rat X::Immutable: push List X::Temporal::OutOfRange: Day 30 1..29 X::OutOfRange: Index 2 0..0 X::Str::Numeric: 5 foo at 1, trailing characters after number X::Numeric::Real: imaginary part not zero X::Cannot::Lazy: .elems X::Cannot::Empty: pop Array X::Numeric::DivideByZero: div 1 X::Constructor::Positional: Mu X::Hash::Store::OddNumber: 1 X::StubCode: unfinished
The editor’s engine, Raku++, prints something else here
(nothing on standard output; standard error says:)
No such method 'value' for invocant of type 'X::Assignment::RO'
(X::Method::NotFound)
in block <unit> at example.raku line 2
2 | say $!.^name, ": ", $!.value, " ", $!.typename;Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
A method called on Nil is the exception to the rule: it answers Nil and throws nothing (see Nil, Any and the Undefined).