捕获控制
Source: Dev.to
(请提供您希望翻译的文章正文内容,我将把它翻译成简体中文并保留原有的格式和代码块。)
UPPER 案例 – 第 6 部分:捕获异常的 Phasers
CATCH
许多编程语言都有 try / catch 机制。虽然 Raku 编程语言 确实拥有一个 try 语句前缀 并且 确实拥有一个 CATCH phaser,但通常 不应同时使用两者。
在 Raku 中 任何作用域只能有 一个 CATCH 块。该块中的代码会在该作用域内出现任何运行时异常时立即执行,异常对象会以 $_ 的形式被主题化(参见文档)。
- 这就是为什么不能有
CATCHthunk:它需要一个作用域来设置$_,而不影响该作用域之外的任何东西。 CATCH块放在作用域的何处并不重要,但为保持清晰,建议尽可能早地放置它(而不是在作用域末尾“隐藏”它)。阅读代码时,你会立刻看到异常处理的特殊行为。
处理异常
让我们再次用一个人为的例子来说明:
{
CATCH {
when X::AdHoc {
say "naughty: $_.message()";
}
}
die "urgh"; # 抛出一个 X::AdHoc 异常
say "after";
}
say "alive still";
运行上述代码会输出:
naughty: urgh
alive still
注意: 用 when 匹配 $_ 中的异常实际上会禁用该异常,因此它不会在作用域退出时重新抛出。正因为如此,say "alive still" 被执行。其他类型的错误不会被禁用,尽管你可以使用 default 块来实现(参见文档)。
细心的读者会注意到 say "after" 没有被执行。原因是当前作用域在离开时相当于执行了 return Nil,而 CATCH 块所在的作用域正是如此。
如果异常是良性的,你可以通过在异常对象上调用 .resume 方法,使执行继续到导致异常的语句之后的那条语句:
{
CATCH { .resume }
die "urgh";
say "after";
}
say "alive still";
输出:
after
alive still
重要提示: 并非所有异常都可以恢复!例如,除以
0的错误是不可恢复的:
CATCH { .resume }
say 1/0;
示例输出:
This exception is not resumable
in block foo at bar line 42
尝试代码
try 语句前缀(接受 thunk 或块)本质上是一个简化版的 CATCH 处理器,它会解除所有错误的激活,设置 $!,并返回 Nil。
say try die "urgh"; # Nil
dd $!; # $! = X::AdHoc.new(payload => "urgh")
大致等价于:
say {
CATCH {
CALLERS:: = $_; # 在正确的作用域中设置 $!
default { } # 解除异常
}
die "urgh";
}(); # Nil
dd $!; # $! = X::AdHoc.new(payload => "urgh")
即使 die "urgh" 是一个 thunk,编译器也会在内部创建自己的作用域来处理 thunk 的返回。
CONTROL
除了运行时异常,Raku 还使用许多其他类型的异常。它们都实现了 X::Control 角色(文档),因此被称为 控制异常(语言指南)。这些异常用于以下 Raku 特性(按字母顺序):
| 控制异常 | 描述 |
|---|---|
done | 对所有 tap 调用 “done” 回调 |
emit | 向 supply 的所有 tap 发送项目 |
last | 退出循环结构 |
next | 在循环中开始下一次迭代 |
proceed | 在 given 块之后恢复执行 |
redo | 重新执行当前循环迭代 |
| return | 从子例程/方法返回 |
| succeed | 退出 given 块 |
| take | 将项目传递给 gather |
| warn | 使用给定的消息发出警告 |
| | 在循环中重新启动迭代 |
就像运行时异常一样,控制异常会自动执行其预期的工作。不过,你 不能 用 CATCH 相位捕获它们;你需要使用 CONTROL 相位(docs)。
手动处理控制异常
假设你遇到了一个恼人的警告……(原文到此结束。)
Source: …
去除警告输出
您可能想要抑制运行以下代码时出现的警告:
say $_ ~ "foo";
警告信息如下:
Use of uninitialized value element of type Any in string context.
Methods .^name, .raku, .gist, or .say can be used to stringify it to something meaningful.
in block foo at bar line 42
foo
使用 CONTROL 相位
警告是 控制异常,因此可以用 CONTROL 相位捕获它们:
CONTROL {
when CX::Warn { .resume }
}
say $_ ~ "foo";
现在只会显示期望的输出:
foo
quietly 语句前缀
Raku 还提供了一个快捷方式:quietly 语句前缀。
quietly say $_ ~ "foo";
为警告获取完整堆栈跟踪
默认的警告处理只会显示触发警告的调用点。
如果需要完整的回溯信息,可以使用一个 CONTROL 块来打印消息和堆栈跟踪:
CONTROL {
when CX::Warn {
note .message;
note .backtrace.join;
.resume;
}
}
say $_ ~ "foo";
输出示例:
Use of uninitialized value element of type Any in string context.
Methods .^name, .raku, .gist, or .say can be used to stringify it to something meaningful.
in sub warn at SETTING::src/core.c/control.rakumod line 267
in method Str at SETTING::src/core.c/Mu.rakumod line 817
in method join at SETTING::src/core.c/List.rakumod line 1200
in sub infix: at SETTING::src/core.c/Str.rakumod line 3995
in block foo at bar line 42
将警告转为运行时异常
如果希望警告导致程序中止,可以将其重新抛出为异常:
CONTROL {
when CX::Warn { .throw }
}
say $_ ~ "foo";
结果:
Use of uninitialized value $_ of type Any in string context.
Methods .^name, .raku, .gist, or .say can be used to stringify it to something meaningful.
in block foo at bar line 42
由于在抛出异常后执行停止,"foo" 那一行永远不会出现。
构建您自己的控制异常
定义异常类
class Frobnicate does X::Control {
has $.message;
}
抛出异常的辅助子例程
sub frobnicate($message) {
Frobnicate.new(:$message).throw
}
处理自定义异常
CONTROL {
when Frobnicate {
say "Caught a frobnication: $_.message()";
.resume;
}
}
使用子例程
say "before";
frobnicate "This";
say "after";
输出:
before
Caught a frobnication: This
after
最后说明
CATCH和CONTROL相位器是 基于作用域 的;在运行时无法检查Block以发现它是否包含CATCH或CONTROL相位器。- 在同一作用域中只能存在其中一个相位器,处理已嵌入块的字节码中。
- Raku 的异常处理基于 分段续延。想深入了解,请参阅文章 “Continuations in NQP”。
结论
CATCH处理 致命 异常。CONTROL处理 “正常” 控制流异常,例如next、last、warn等。- 你可以创建自己的控制异常,尽管它们的实际用处可能有限。
- Raku 中的所有异常处理都依赖于分段续延。
这标志着 Raku 编程语言中“UPPER 语言元素案例”第六集的结束,敬请期待更多内容!