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($path{
    X::IO::DoesNotExist.new(:$path:trying("frobnicate")).throw
      unless $path.IO.e;
    # do the actual frobnication 
}
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(:$path:trying("frobnicate"));

捕获异常§

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

CATCH {
    when X::IO { $*ERR.say: "some kind of IO exception was caught!" }
}
X::IO::DoesNotExist.new(:$path: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 {
     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 块放在其适用的词法范围的开头,以便代码的随意读者可以立即看到正在发生一些特殊的事情。

异常处理程序和封闭块§

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 编译指示 并包含一个隐式的 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.raku line 21

因为 CATCH 块只处理 die 语句抛出的 X::AdHoc 异常,而不处理 E 异常。在没有 CATCH 块的情况下,所有异常都将被包含并丢弃,如上所述。resume 将在异常抛出后立即恢复执行;在本例中,在 die 语句中。有关此方面的更多信息,请参阅关于 异常恢复 的部分。

try 块是一个正常的代码块,因此它将最后一个语句视为其自身的返回值。因此,我们可以将其用作右侧。

say try { +"99999" } // "oh no"# OUTPUT: «99999␤» 
say try { +"hello" } // "oh no"# OUTPUT: «oh no␤» 

try 块通过返回表达式的返回值或在抛出异常时返回 Nil 来间接支持 else 块。

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 编译指示在它的作用域内立即抛出发生的异常,但这样做会从抛出异常的地方调用 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 方法显式地抛出异常。

此示例抛出一个 X::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;
 
# OUTPUT: «foo 
#          in block <unit> at my-script.raku:1»

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

{
    X::AdHoc.new(:payload<foo>).throw;
    "OHAI".say;
    CATCH {
        when X::AdHoc { }
    }
}
 
"OBAI".say;
 
# OUTPUT: «OBAI␤»

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

异常的恢复§

异常会中断控制流并将其从抛出异常的语句之后的语句中转移。用户处理的任何异常都可以恢复,控制流将从抛出异常的语句之后的语句继续执行。为此,请在异常对象上调用方法 .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 的调用内部,该调用通过 return 语句将 not returning 值分配给它。

未捕获的异常§

如果抛出异常但未捕获,它会导致程序以非零状态代码退出,并且通常会向程序的标准错误流打印一条消息。此消息是通过在异常对象上调用 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

控制异常§

当抛出执行 X::Control 角色的异常时,会引发控制异常(自 Rakudo 2019.03 起)。它们通常由某些 关键字 抛出,并由适当的 阶段 自动处理或由其处理。任何未处理的控制异常都会转换为普通异常。

{ returnCATCH { default { $*ERR.say: .^name''.Str } } }
# OUTPUT: «X::ControlFlow::Return: Attempt to return outside of any Routine␤» 
# was CX::Return