从C FFI调用Rust时如何避免panic,而不需要创建线程

15

我正在为Duktape JavaScript解释器编写Rust包装器(https://github.com/emk/duktape-rs)。在正常使用情况下,调用堆栈将如下所示:

  1. Rust:任意应用程序代码。
  2. Rust:我的库包装器。
  3. C:Duktape解释器。
  4. Rust:我的Rust代码。
  5. Rust:进入应用程序代码的任意回调。

如果(5)从内部非Rust调用帧中调用panic!会发生什么?根据IRC上的各种Rust开发人员的说法,尝试从像(3)这样的非Rust调用帧内部调用panic!可能会导致未定义的行为。

但是根据Rust文档,捕获panic!的唯一方法是使用std::task::try,它会生成一个额外的线程。还有一个rustrt::unwind::try,在单个线程内不能嵌套两次,除其他限制外。

Benjamin Herr提出的一种解决方案是,如果(5)中的代码发生恐慌,则中止进程。我将他的解决方案打包成abort_on_panic,它似乎可以工作,包括“崩溃整个程序,但至少不会使事情变得微妙”:

abort_on_panic!("cannot panic inside this block", {
    panic!("something went wrong!");
});

但是有没有一种方法可以模拟std::task::try的行为,而不需要创建线程或任务的开销呢?

2个回答

10

从Rust 1.9.0开始,您可以使用panic::catch_unwind来恢复错误:

use std::panic;

fn main() {
    let result = panic::catch_unwind(|| {
        panic!("oh no!");
    });
    assert!(result.is_err());
}

使用 panic::resume_unwind 将它传递给下一层同样很容易:

use std::panic;

fn main() {
    let result = panic::catch_unwind(|| {
        panic!("oh no!");
    });

    if let Err(e) = result {
        panic::resume_unwind(e);
    }
}

4

编辑注:此答案的发布时间早于 Rust 1.0 版本,因此不一定准确。其他答案仍然包含有价值的信息。

你不能“捕获”panic!。它终止当前线程的执行。因此,在不启动新线程来隔离报错的情况下,将会中止所在的线程。


谢谢!std::task::try是否真的需要实际生成一个新的操作系统线程?它似乎可以被实现为保存一些运行时状态,使用同一操作系统线程调用“任务”,并在完成时恢复运行时状态。这将保持与当前版本相同的公共API,但可能节省每次JavaScript调用Rust时调用pthread_new的成本。 - emk
这比 Rust 高级一些。 - Steve Klabnik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接