Raku 中的异常是保存有关错误信息的对象。例如,错误可能是意外接收数据或网络连接不再可用,或者丢失文件。异常对象存储的信息是关于错误条件的人类可读消息,错误引发的回溯等等。

所有内置异常都继承自 Exception,它提供了一些基本行为,包括回溯的存储和回溯打印机的接口。

热异常

通过调用带有描述错误的 die 函数来使用热异常:

  1. die "oops, something went wrong";
  2. # RESULT: «oops, something went wrong in block <unit> at my-script.p6:1
  3. »

值得注意的是,die 会将错误消息打印到标准错误 $*ERR

类型化的异常

类型化异常提供有关异常对象中存储的错误的更多信息。

例如,如果在对象上执行 .zombie copy 时,所需的路径 foo/bar 变得不可用,则可以引发 X::IO::DoesNotExist异常:

  1. die X::IO::DoesNotExist.new(:path("foo/bar"), :trying("zombie copy"))
  2. # RESULT: «Failed to find 'foo/bar' while trying to do '.zombie copy'
  3. # in block <unit> at my-script.p6:1»

请注意对象如何为回溯提供有关出错的信息。代码的用户现在可以更轻松地找到并纠正问题。

捕获异常

通过提供 CATCH 块可以处理异常情况:

  1. die X::IO::DoesNotExist.new(:path("foo/bar"), :trying("zombie copy"));
  2. CATCH {
  3. when X::IO { $*ERR.say: "some kind of IO exception was caught!" }
  4. }
  5. # 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 语句。此示例打印出与普通回溯打印机几乎相同的信息。

  1. CATCH {
  2. default {
  3. $*ERR.say: .message;
  4. for .backtrace.reverse {
  5. next if .file.starts-with('SETTING::');
  6. next unless .subname;
  7. $*ERR.say: " in block {.subname} at {.file} line {.line}";
  8. }
  9. }
  10. }

请注意,匹配目标是一个角色。要允许用户定义的异常以相同的方式匹配,它们必须实现给定的角色。仅存在于同一名称空间中看起来相似但在 CATCH 块中不匹配。

异常处理程序和闭合块

CATCH 处理异常之后,退出包围 CATCH 块的块。

换句话说,即使成功处理异常,封闭块中的其余代码也永远不会被执行。

  1. die "something went wrong ...";
  2. CATCH {
  3. # will definitely catch all the exception
  4. default { .Str.say; }
  5. }
  6. say "This won't be said."; # but this line will be never reached since
  7. # the enclosing block will be exited immediately
  8. # OUTPUT: «something went wrong ...
  9. »

和这个作对比:

  1. CATCH {
  2. CATCH {
  3. default { .Str.say; }
  4. }
  5. die "something went wrong ...";
  6. }
  7. say "Hi! I am at the outer block!"; # OUTPUT: «Hi! I am at the outer block!
  8. »

有关如何将控制权返回到发生异常的位置,请参阅恢复异常

try 块

try 块是一个普通块,它隐式打开 use fatal pragma 编译指示,并包含一个隐式 CATCH 块,它会删除异常,这意味着您可以使用它来包含它们。 捕获的异常存储在$中! 变量,它包含 Exception 类型的值。

像这样的普通块将会失败:

  1. {
  2. my $x = +"a";
  3. say $x.^name;
  4. } # OUTPUT: «Failure
  5. »

但是,try 块将包含异常并将其放入 $! 变量:

  1. try {
  2. my $x = +"a";
  3. say $x.^name;
  4. }
  5. if $! { say "Something failed!" } # OUTPUT: «Something failed!
  6. »
  7. say $!.^name; # OUTPUT: «X::Str::Numeric
  8. »

在这样的块中抛出的任何异常都将被 CATCH 块捕获,无论是隐式的还是由用户提供的。在后一种情况下,任何未处理的异常都将被重新抛出。如果您选择不处理异常,则它们将被块包含。

  1. try {
  2. die "Tough luck";
  3. say "Not gonna happen";
  4. }
  5. try {
  6. fail "FUBAR";
  7. }

在上面的两个 try 块中,异常将包含在块中,但不会运行 say 语句。但我们可以处理它们:

  1. class E is Exception { method message() { "Just stop already!" } }
  2. try {
  3. E.new.throw; # this will be local
  4. say "This won't be said.";
  5. }
  6. say "I'm alive!";
  7. try {
  8. CATCH {
  9. when X::AdHoc { .Str.say; .resume }
  10. }
  11. die "No, I expect you to DIE Mr. Bond!";
  12. say "I'm immortal.";
  13. E.new.throw;
  14. say "No, you don't!";
  15. }

这会输出:

  1. I'm alive!
  2. No, I expect you to DIE Mr. Bond!
  3. I'm immortal.
  4. Just stop already!
  5. in block <unit> at exception.p6 line 21

由于 CATCH 块只处理 die 语句抛出的 X::AdHoc 异常,而不处理 E 异常。 如果没有 CATCH 块,所有异常都将被包含和删除,如上所示。 恢复将在异常抛出后立即恢复执行; 在这种情况下,在 die 语句中。 有关详细信息,请参阅有关恢复异常的部分。

try-block 是一个普通的块,因此将其最后一个语句视为自身的返回值。 因此,我们可以将其用作右手边。

  1. say try { +"99999" } // "oh no"; # OUTPUT: «99999
  2. »
  3. say try { +"hello" } // "oh no"; # OUTPUT: «oh no
  4. »

通过返回表达式的返回值来间接尝试块支持 else 块,如果抛出异常,则返回 Nil

  1. with try +"♥" {
  2. say "this is my number: $_"
  3. } else {
  4. say "not my number!"
  5. }
  6. # OUTPUT: «not my number!
  7. »

try 也可以和语句一块用而非块:

  1. say try "some-filename.txt".IO.slurp // "sane default";
  2. # OUTPUT: «sane default
  3. »

try 实际导致的是,通过 use fatal pragma,立即抛出在其范围内发生的异常,但通过这样做,从抛出异常的点调用 CATCH 块,定义其范围。

  1. my $error-code = "333";
  2. sub bad-sub {
  3. die "Something bad happened";
  4. }
  5. try {
  6. my $error-code = "111";
  7. bad-sub;
  8. CATCH {
  9. default {
  10. say "Error $error-code ", .^name, ': ',.Str
  11. }
  12. }
  13. }
  14. # OUTPUT: «Error 111 X::AdHoc: Something bad happened
  15. »

抛出异常

可以使用Exception对象的 .throw 方法显式抛出异常。

此示例抛出 AdHoc 异常,捕获它并允许代码通过调用 .resume 方法从异常点继续。

  1. {
  2. X::AdHoc.new(:payload<foo>).throw;
  3. "OHAI".say;
  4. CATCH {
  5. when X::AdHoc { .resume }
  6. }
  7. }
  8. "OBAI".say;
  9. # OUTPUT: «OHAI
  10. OBAI
  11. »

如果 CATCH 块与抛出的异常不匹配,则将异常的有效负载传递给回溯打印机制。

  1. {
  2. X::AdHoc.new(:payload<foo>).throw;
  3. "OHAI".say;
  4. CATCH { }
  5. }
  6. "OBAI".say;
  7. # RESULT: «foo
  8. # in block <unit> at my-script.p6:1»

下一个示例不会从异常点恢复。相反,它会在封闭块之后继续,因为捕获了异常,然后在 CATC H块之后控制继续。

  1. {
  2. X::AdHoc.new(:payload<foo>).throw;
  3. "OHAI".say;
  4. CATCH {
  5. when X::AdHoc { }
  6. }
  7. }
  8. "OBAI".say;
  9. # OUTPUT: «OBAI
  10. »

throw 可以被视为 die 的方法形式,只是在这种特殊情况下,例程的 sub 和 method 形式有不同的名称。

异常恢复

异常会中断控制流并将其从抛出语句后的语句中转移出去。可以恢复用户处理的任何异常,并且控制流将继续使用抛出异常的语句之后的语句。为此,请在异常对象上调用方法 .resume

  1. CATCH { when X::AdHoc { .resume } } # this is step 2
  2. die "We leave control after this."; # this is step 1
  3. say "We have continued with control flow."; # this is step 3

恢复将在导致异常的语句之后和最里面的调用帧中发生

  1. sub bad-sub {
  2. die "Something bad happened";
  3. return "not returning";
  4. }
  5. {
  6. my $return = bad-sub;
  7. say "Returned $return";
  8. CATCH {
  9. default {
  10. say "Error ", .^name, ': ',.Str;
  11. $return = '0';
  12. .resume;
  13. }
  14. }
  15. }
  16. # OUTPUT:
  17. # Error X::AdHoc: Something bad happened
  18. # Returned not returning

在这种情况下,.resume 将转到在 die 语句之后发生的 return 语句。请注意,$return 的赋值不起作用,因为 CATCH 语句发生在对 bad-sub 的调用中,bad-sub 通过 return 语句为其分配不返回的值。

未捕获的异常

如果抛出异常但未捕获异常,则会导致程序以非零状态代码退出,并且通常会将消息输出到程序的标准错误流。通过在异常对象上调用 gist 方法获得此消息。您可以使用它来抑制打印回溯的默认行为以及消息:

  1. class X::WithoutLineNumber is X::AdHoc {
  2. multi method gist(X::WithoutLineNumber:D:) {
  3. $.payload
  4. }
  5. }
  6. die X::WithoutLineNumber.new(payload => "message")
  7. # prints "message\n" to $*ERR and exits, no backtrace

控制异常

某些关键字会引发控制异常,并自动或由相应的 phaser 处理。任何未处理的控制异常都将转换为正常异常。

  1. { return; CATCH { default { $*ERR.say: .^name, ': ',.Str } } }
  2. # OUTPUT: «X::ControlFlow::Return: Attempt to return outside of any Routine
  3. »
  4. # was CX::Return