Raku 中的异常是包含错误信息的对象。错误可能是,例如,意外接收数据或网络连接不再可用,或文件丢失。异常对象存储的信息包括,例如,关于错误条件的人类可读消息、引发错误的回溯等等。
所有内置异常都继承自 Exception
,它提供了一些基本行为,包括回溯的存储和回溯打印机的接口。
临时 异常§
临时异常可以通过调用 die 并提供错误描述来使用
die "oops, something went wrong";# OUTPUT: «oops, something went wrong in block <unit> at my-script.raku:1»
值得注意的是,die
会将错误消息打印到标准错误流 $*ERR
。
类型化异常§
类型化异常提供了更多关于存储在异常对象中的错误的信息。
例如,如果在对对象执行 .frobnicate
时,所需的路径 foo/bar
变得不可用,则会抛出一个 X::IO::DoesNotExist
异常
method frobnicate()
frobnicate("foo/bar");# OUTPUT: «Failed to find 'foo/bar' while trying to do '.frobnicate'# in block <unit> at my-script.raku:1»
注意对象是如何提供包含错误信息的反向跟踪的。代码的用户现在可以更容易地找到并纠正问题。
除了在 X::IO::DoesNotExist
对象上调用 .throw
方法之外,还可以使用该对象作为 die
的参数
die X::IO::DoesNotExist.new(:, :trying("frobnicate"));
捕获异常§
可以通过提供 CATCH
块来处理异常情况
CATCHX::IO::DoesNotExist.new(:, :trying("frobnicate")).throw# 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
块中保存 Exception
。
CATCH
请注意,匹配目标是一个角色。为了允许用户定义的异常以相同的方式匹配,它们必须实现给定的角色。仅仅存在于同一个命名空间中看起来很像,但在 CATCH
块中不会匹配。
请注意,CATCH
块语义适用于定义它的整个词法范围,无论它在该词法范围内的何处定义。因此,建议将任何 CATCH
块放在其适用的词法范围的开头,以便代码的随意读者可以立即看到正在发生一些特殊的事情。
异常处理程序和封闭块§
在 CATCH
处理完异常后,将退出包含 CATCH
块的块。
换句话说,即使异常成功处理,封闭块中的其余代码也永远不会执行。
die "something went wrong ...";CATCHsay "This won't be said."; # but this line will be never reached since# the enclosing block will be exited immediately# OUTPUT: «something went wrong ...»
与之相比
CATCHsay "Hi! I am at the outer block!"; # OUTPUT: «Hi! I am at the outer block!»
请参阅 异常的恢复,了解如何将控制权返回到异常起源的地方。
try
块§
try
块是一个正常的块,它隐式地打开 use fatal
编译指示 并包含一个隐式的 CATCH
块,该块会丢弃异常,这意味着你可以使用它来包含它们。捕获的异常存储在 $!
变量中,该变量保存类型为 Exception
的值。
像这样的普通块只会失败
# OUTPUT: «Failure»
但是,try
块将包含异常并将其放入 $!
变量中
tryif $! # OUTPUT: «Something failed!»say $!.^name; # OUTPUT: «X::Str::Numeric»
在这样的块中抛出的任何异常都将被 CATCH
块捕获,无论是隐式的还是用户提供的。在后一种情况下,任何未处理的异常都将被重新抛出。如果你选择不处理异常,它们将被块包含。
trytry
在上面的两个 try
块中,异常都将包含在块中,但 say
语句不会运行。不过,我们可以处理它们
is Exceptiontrysay "I'm alive!";try
这将输出
I'm alive! No, I expect you to DIE Mr. Bond! I'm immortal. Just stop already! in block <unit> at exception.raku line 21
因为 CATCH
块只处理 die
语句抛出的 X::AdHoc
异常,而不处理 E
异常。在没有 CATCH
块的情况下,所有异常都将被包含并丢弃,如上所述。resume
将在异常抛出后立即恢复执行;在本例中,在 die
语句中。有关此方面的更多信息,请参阅关于 异常恢复 的部分。
try
块是一个正常的代码块,因此它将最后一个语句视为其自身的返回值。因此,我们可以将其用作右侧。
say try // "oh no"; # OUTPUT: «99999»say try // "oh no"; # OUTPUT: «oh no»
try
块通过返回表达式的返回值或在抛出异常时返回 Nil
来间接支持 else
块。
with try +"♥"else# OUTPUT: «not my number!»
try
也可以与语句而不是代码块一起使用,即作为 语句前缀
say try "some-filename.txt".IO.slurp // "sane default";# OUTPUT: «sane default»
try
实际上通过 use fatal
编译指示在它的作用域内立即抛出发生的异常,但这样做会从抛出异常的地方调用 CATCH
块,从而定义它的作用域。
my = "333";sub bad-subtry# OUTPUT: «Error 111 X::AdHoc: Something bad happened»
抛出异常§
可以使用 Exception
对象的 .throw
方法显式地抛出异常。
此示例抛出一个 X::AdHoc
异常,捕获它并通过调用 .resume
方法允许代码从异常点继续执行。
"OBAI".say;# OUTPUT: «OHAIOBAI»
如果 CATCH
块与抛出的异常不匹配,则异常的有效负载将传递给回溯打印机制。
"OBAI".say;# OUTPUT: «foo# in block <unit> at my-script.raku:1»
下一个示例不会从异常点恢复。相反,它在封闭块之后继续执行,因为异常被捕获,然后控制在 CATCH
块之后继续执行。
"OBAI".say;# OUTPUT: «OBAI»
throw
可以被视为 die
的方法形式,只是在这种特殊情况下,例程的子例程和方法形式具有不同的名称。
异常的恢复§
异常会中断控制流并将其从抛出异常的语句之后的语句中转移。用户处理的任何异常都可以恢复,控制流将从抛出异常的语句之后的语句继续执行。为此,请在异常对象上调用方法 .resume
。
CATCH # this is step 2die "We leave control after this."; # this is step 1say "We have continued with control flow."; # this is step 3
恢复将在导致异常的语句之后立即发生,并在最内层的调用帧中发生
sub bad-sub# OUTPUT:# Error X::AdHoc: Something bad happened# Returned not returning
在这种情况下,.resume
正在到达 die
语句之后发生的 return
语句。请注意,对 $return
的赋值没有生效,因为 CATCH
语句发生在对 bad-sub
的调用内部,该调用通过 return
语句将 not returning
值分配给它。
未捕获的异常§
如果抛出异常但未捕获,它会导致程序以非零状态代码退出,并且通常会向程序的标准错误流打印一条消息。此消息是通过在异常对象上调用 gist
方法获得的。您可以使用它来抑制打印回溯以及消息的默认行为
is X::AdHocdie X::WithoutLineNumber.new(payload => "message")# prints "message\n" to $*ERR and exits, no backtrace
控制异常§
当抛出执行 X::Control
角色的异常时,会引发控制异常(自 Rakudo 2019.03 起)。它们通常由某些 关键字 抛出,并由适当的 阶段 自动处理或由其处理。任何未处理的控制异常都会转换为普通异常。
# OUTPUT: «X::ControlFlow::Return: Attempt to return outside of any Routine»# was CX::Return