Raku 中的异常是保存有关错误信息的对象。例如,错误可能是意外接收数据或网络连接不再可用,或者丢失文件。异常对象存储的信息是关于错误条件的人类可读消息,错误引发的回溯等等。
所有内置异常都继承自 Exception,它提供了一些基本行为,包括回溯的存储和回溯打印机的接口。
热异常
通过调用带有描述错误的 die 函数来使用热异常:
die "oops, something went wrong";
# RESULT: «oops, something went wrong in block <unit> at my-script.p6:1
»
值得注意的是,die
会将错误消息打印到标准错误 $*ERR
。
类型化的异常
类型化异常提供有关异常对象中存储的错误的更多信息。
例如,如果在对象上执行 .zombie copy
时,所需的路径 foo/bar
变得不可用,则可以引发 X::IO::DoesNotExist异常:
die X::IO::DoesNotExist.new(:path("foo/bar"), :trying("zombie copy"))
# RESULT: «Failed to find 'foo/bar' while trying to do '.zombie copy'
# in block <unit> at my-script.p6:1»
请注意对象如何为回溯提供有关出错的信息。代码的用户现在可以更轻松地找到并纠正问题。
捕获异常
通过提供 CATCH
块可以处理异常情况:
die X::IO::DoesNotExist.new(:path("foo/bar"), :trying("zombie copy"));
CATCH {
when X::IO { $*ERR.say: "some kind of IO exception was caught!" }
}
# OUTPUT: «some kind of IO exception was caught!»
在这里,我们说如果发生 X::IO
类型的任何异常,那么消息 some kind of IO exception was caught!
会被发送到 stderr,这是 $*ERR.say
所做的事情,在那一刻构成标准错误设备的任何内容上显示,默认情况下可能是控制台。
CATCH
块使用类似于 given/when
对选项进行智能匹配的智能匹配,因此可以捕获和处理 when
块内的各种类别的异常。
要处理所有异常,请使用 default
语句。此示例打印出与普通回溯打印机几乎相同的信息。
CATCH {
default {
$*ERR.say: .message;
for .backtrace.reverse {
next if .file.starts-with('SETTING::');
next unless .subname;
$*ERR.say: " in block {.subname} at {.file} line {.line}";
}
}
}
请注意,匹配目标是一个角色。要允许用户定义的异常以相同的方式匹配,它们必须实现给定的角色。仅存在于同一名称空间中看起来相似但在 CATCH
块中不匹配。
异常处理程序和闭合块
在 CATCH
处理异常之后,退出包围 CATCH
块的块。
换句话说,即使成功处理异常,封闭块中的其余代码也永远不会被执行。
die "something went wrong ...";
CATCH {
# will definitely catch all the exception
default { .Str.say; }
}
say "This won't be said."; # but this line will be never reached since
# the enclosing block will be exited immediately
# OUTPUT: «something went wrong ...
»
和这个作对比:
CATCH {
CATCH {
default { .Str.say; }
}
die "something went wrong ...";
}
say "Hi! I am at the outer block!"; # OUTPUT: «Hi! I am at the outer block!
»
有关如何将控制权返回到发生异常的位置,请参阅恢复异常。
try 块
try
块是一个普通块,它隐式打开 use fatal pragma 编译指示,并包含一个隐式 CATCH
块,它会删除异常,这意味着您可以使用它来包含它们。 捕获的异常存储在$中! 变量,它包含 Exception
类型的值。
像这样的普通块将会失败:
{
my $x = +"a";
say $x.^name;
} # OUTPUT: «Failure
»
但是,try
块将包含异常并将其放入 $!
变量:
try {
my $x = +"a";
say $x.^name;
}
if $! { say "Something failed!" } # OUTPUT: «Something failed!
»
say $!.^name; # OUTPUT: «X::Str::Numeric
»
在这样的块中抛出的任何异常都将被 CATCH
块捕获,无论是隐式的还是由用户提供的。在后一种情况下,任何未处理的异常都将被重新抛出。如果您选择不处理异常,则它们将被块包含。
try {
die "Tough luck";
say "Not gonna happen";
}
try {
fail "FUBAR";
}
在上面的两个 try
块中,异常将包含在块中,但不会运行 say
语句。但我们可以处理它们:
class E is Exception { method message() { "Just stop already!" } }
try {
E.new.throw; # this will be local
say "This won't be said.";
}
say "I'm alive!";
try {
CATCH {
when X::AdHoc { .Str.say; .resume }
}
die "No, I expect you to DIE Mr. Bond!";
say "I'm immortal.";
E.new.throw;
say "No, you don't!";
}
这会输出:
I'm alive!
No, I expect you to DIE Mr. Bond!
I'm immortal.
Just stop already!
in block <unit> at exception.p6 line 21
由于 CATCH
块只处理 die
语句抛出的 X::AdHoc
异常,而不处理 E
异常。 如果没有 CATCH
块,所有异常都将被包含和删除,如上所示。 恢复将在异常抛出后立即恢复执行; 在这种情况下,在 die
语句中。 有关详细信息,请参阅有关恢复异常的部分。
try-block
是一个普通的块,因此将其最后一个语句视为自身的返回值。 因此,我们可以将其用作右手边。
say try { +"99999" } // "oh no"; # OUTPUT: «99999
»
say try { +"hello" } // "oh no"; # OUTPUT: «oh no
»
通过返回表达式的返回值来间接尝试块支持 else
块,如果抛出异常,则返回 Nil。
with try +"♥" {
say "this is my number: $_"
} else {
say "not my number!"
}
# OUTPUT: «not my number!
»
try
也可以和语句一块用而非块:
say try "some-filename.txt".IO.slurp // "sane default";
# OUTPUT: «sane default
»
try
实际导致的是,通过 use fatal pragma
,立即抛出在其范围内发生的异常,但通过这样做,从抛出异常的点调用 CATCH
块,定义其范围。
my $error-code = "333";
sub bad-sub {
die "Something bad happened";
}
try {
my $error-code = "111";
bad-sub;
CATCH {
default {
say "Error $error-code ", .^name, ': ',.Str
}
}
}
# OUTPUT: «Error 111 X::AdHoc: Something bad happened
»
抛出异常
可以使用Exception对象的 .throw
方法显式抛出异常。
此示例抛出 AdHoc 异常,捕获它并允许代码通过调用 .resume
方法从异常点继续。
{
X::AdHoc.new(:payload<foo>).throw;
"OHAI".say;
CATCH {
when X::AdHoc { .resume }
}
}
"OBAI".say;
# OUTPUT: «OHAI
OBAI
»
如果 CATCH 块与抛出的异常不匹配,则将异常的有效负载传递给回溯打印机制。
{
X::AdHoc.new(:payload<foo>).throw;
"OHAI".say;
CATCH { }
}
"OBAI".say;
# RESULT: «foo
# in block <unit> at my-script.p6:1»
下一个示例不会从异常点恢复。相反,它会在封闭块之后继续,因为捕获了异常,然后在 CATC H块之后控制继续。
{
X::AdHoc.new(:payload<foo>).throw;
"OHAI".say;
CATCH {
when X::AdHoc { }
}
}
"OBAI".say;
# OUTPUT: «OBAI
»
throw 可以被视为 die 的方法形式,只是在这种特殊情况下,例程的 sub 和 method 形式有不同的名称。
异常恢复
异常会中断控制流并将其从抛出语句后的语句中转移出去。可以恢复用户处理的任何异常,并且控制流将继续使用抛出异常的语句之后的语句。为此,请在异常对象上调用方法 .resume
。
CATCH { when X::AdHoc { .resume } } # this is step 2
die "We leave control after this."; # this is step 1
say "We have continued with control flow."; # this is step 3
恢复将在导致异常的语句之后和最里面的调用帧中发生
sub bad-sub {
die "Something bad happened";
return "not returning";
}
{
my $return = bad-sub;
say "Returned $return";
CATCH {
default {
say "Error ", .^name, ': ',.Str;
$return = '0';
.resume;
}
}
}
# OUTPUT:
# Error X::AdHoc: Something bad happened
# Returned not returning
在这种情况下,.resume
将转到在 die
语句之后发生的 return
语句。请注意,$return
的赋值不起作用,因为 CATCH 语句发生在对 bad-sub
的调用中,bad-sub
通过 return
语句为其分配不返回的值。
未捕获的异常
如果抛出异常但未捕获异常,则会导致程序以非零状态代码退出,并且通常会将消息输出到程序的标准错误流。通过在异常对象上调用 gist
方法获得此消息。您可以使用它来抑制打印回溯的默认行为以及消息:
class X::WithoutLineNumber is X::AdHoc {
multi method gist(X::WithoutLineNumber:D:) {
$.payload
}
}
die X::WithoutLineNumber.new(payload => "message")
# prints "message\n" to $*ERR and exits, no backtrace
控制异常
某些关键字会引发控制异常,并自动或由相应的 phaser 处理。任何未处理的控制异常都将转换为正常异常。
{ return; CATCH { default { $*ERR.say: .^name, ': ',.Str } } }
# OUTPUT: «X::ControlFlow::Return: Attempt to return outside of any Routine
»
# was CX::Return