AssertUnwindSafe 如何与 CatchUnwind future 一起使用

Mar*_*eth 5 future rust async-await

我希望能够传递对函数的可变引用,但捕获可能来自该函数的展开。目的是用于编写一些测试包装器(设置、拆卸),而不是一般的错误处理。

如果我使用典型的同步代码,我可以让它编译并工作......

struct MyStruct {
    n: u32
}

fn my_func(s: &mut MyStruct) {
    s.n += 1;
    panic!("Oh no!");
}

fn main() {
    let mut ctx = MyStruct { n: 1 };
    let mut wrapper = std::panic::AssertUnwindSafe(&mut ctx);
    let result = std::panic::catch_unwind(move || {
        my_func(*wrapper);
    });
    
    // Do some cleanup of `ctx` here.

    if let Err(err) = result {
        std::panic::resume_unwind(err);
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,我无法弄清楚如何使用 futures 和 async/await 来做到这一点。在这种情况下,我将尝试调用已声明为异步的函数。我尝试过各种方法,例如下面的代码:

async fn run_async(s: &mut MyStruct) {
    s.n += 1;
    panic!("Oh no!");
}

#[tokio::main]
async fn main() {
    let mut ctx = MyStruct { n : 1 };
    let wrapper = std::panic::AssertUnwindSafe(&mut ctx);
    let result = async move {
        run_async(*wrapper).catch_unwind().await
    }.await;
    
    println!("{:?}", result);
}
Run Code Online (Sandbox Code Playgroud)

但是,我通常会遇到如下错误:

该类型&mut MyStruct可能无法安全地跨过展开边界传输。

我相信这AssertUnwindSafe应该有助于解决这些问题,就像他们对同步代码所做的那样。但显然在 AssertUnwindSafe 和 async/await 的交叉点上有一些我不理解的地方。

Apl*_*123 6

其中std::panic::catch_unwind,提供的闭包必须是UnwindSafe,并且在内部使用可变引用将使闭包无法实现UnwindSafe。这就是为什么包装引用并移动它反而有效的原因。

但是,对于futures::future::FutureExt::catch_unwind,提供的future必须是UnwindSafe,并且由 生成的 futurerun_async并不关心引用是否来自AssertUnwindSafe包装器,因为您在调用它之前将其解包。因此,您应该断言未来本身是安全的:

use futures::future::FutureExt;

struct MyStruct {
    n: i32
}

async fn run_async(s: &mut MyStruct) {
    s.n += 1;
    panic!("Oh no!");
}

#[tokio::main]
async fn main() {
    let mut ctx = MyStruct { n : 1 };
    let result = async move {
        // AssertUnwindSafe moved to the future
        std::panic::AssertUnwindSafe(run_async(&mut ctx)).catch_unwind().await
    }.await;
    
    println!("{:?}", result);
}
Run Code Online (Sandbox Code Playgroud)