Index


Wed Dec 31 23:36:49 CST 2025

上个月写了个 Perl 的错误处理库,DieResult https://github.com/jyi2ya/DieResult,它能将 Perl 异常给转换成类似 Rust Result 的东西,可以添加上下文信息之后再用 unwrap 重新抛出。错误链、出错误位置会形成树形的结构,非常方便调试。

    jyi-00-rust-dev 16:59 (master) ~/dev/DieResult
    0 perl -I. example.pl
    * main example.pl:58
    |
    * Application startup failed
    | main example.pl:51
    | Environment: development
    |
    * main example.pl:43
    | Config path: /cannot_read_this
    | Expected format: TOML
    |
    * Failed to load application configuration
    | main example.pl:36
    |
    |-* attempt 1
    | | main example.pl:22
    | |
    | * Can't open '/cannot_read_this' with mode '<:utf8': 'No such file or directory' at example.pl line 12
    |
    |-* attempt 2
    | | main example.pl:22
    | |
    | * Can't open '/cannot_read_this' with mode '<:utf8': 'No such file or directory' at example.pl line 12
    |
    |-* attempt 3
    | | main example.pl:22
    | |
    | * Can't open '/cannot_read_this' with mode '<:utf8': 'No such file or directory' at example.pl line 12

类似这种风格。

今天想在异步代码里用它,大失败了。

这是因为 DieResult 是用 Perl 函数的 prototype 功能实现的,虽然 wrap 用起来和 eval 差不多,但是前者花括号本质上是闭包,后者则是普通的代码块。闭包里 await 一个 Future 的话,是在非 async 函数中调用 async 方法,这就会遇到经典的函数染色问题。https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/

Perl 的 async 和 await 一般会用 Future::AsyncAwait 来做。这个库实现的 async await 是有栈的,理论上来说可以像 goroutine 那样随地挂起随时切换,但不知为何它并不允许在非 async 函数中使用 await。另一个基于 Coro 的实现,Mojo::AsyncAwait,倒是允许在非 async 函数中 await 一个 Future,不过它已经很久没有更新了,也不是 Mojolicious 中现在使用的方法,所以不用它。

这么看来比较可行的解决方法就只有用 Keyword::Simple 给 Perl 加一个关键字了,在解释执行之前按照我们的意志修改语法树里与模式匹配的部分,哇没想到有一天我会在 Perl 里写过程宏。

搓了一个用 Text::Balanced 提取代码块再用 Keyword::Simple 组装起来的方案,发现不好用。原因是:

    This also means your new keywords can only occur at the beginning of a statement, not embedded in an expression.

它只支持关键字在语句块开头的情况。也就是说不支持 my $var = wrap { ... } 这样的用法。

不过好像也没更好的东西了。Keyword::Declare 底层用的也是 Keyword::Simple,会遇到一样的问题。用 my $var = do { wrap { ... } } 这么套着凑合用用吧。