捕获控制

发布: (2026年2月4日 GMT+8 03:25)
8 分钟阅读
原文: Dev.to

Source: Dev.to

(请提供您希望翻译的文章正文内容,我将把它翻译成简体中文并保留原有的格式和代码块。)

Source: https://example.com/your-original-article

UPPER 案例 – 第 6 部分:捕获异常的 Phasers

CATCH

许多编程语言都有 try / catch 机制。虽然 Raku 编程语言 确实拥有一个 try 语句前缀 并且 确实拥有一个 CATCH phaser,但通常 不应同时使用两者。

在 Raku 中 任何作用域只能有 一个 CATCH 块。该块中的代码会在该作用域内出现任何运行时异常时立即执行,异常对象会以 $_ 的形式被主题化(参见文档)。

  • 这就是为什么不能有 CATCH thunk:它需要一个作用域来设置 $_,而不影响该作用域之外的任何东西。
  • 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在循环中开始下一次迭代
proceedgiven 块之后恢复执行
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

最后说明

  • CATCHCONTROL 相位器是 基于作用域 的;在运行时无法检查 Block 以发现它是否包含 CATCHCONTROL 相位器。
  • 在同一作用域中只能存在其中一个相位器,处理已嵌入块的字节码中。
  • Raku 的异常处理基于 分段续延。想深入了解,请参阅文章 “Continuations in NQP”

结论

  • CATCH 处理 致命 异常。
  • CONTROL 处理 “正常” 控制流异常,例如 nextlastwarn 等。
  • 你可以创建自己的控制异常,尽管它们的实际用处可能有限。
  • Raku 中的所有异常处理都依赖于分段续延。

这标志着 Raku 编程语言中“UPPER 语言元素案例”第六集的结束,敬请期待更多内容!

Back to Blog

相关文章

阅读更多 »

两个 Bug,一个症状

背景:一个关于在 Raku MCP SDK 中实现 SSE 客户端传输的调试战斗故事。任务看似简单:向 th 添加传统 SSE 传输……