繁体   English   中英

从`Fn`调用`FnOnce`

[英]Call `FnOnce` from `Fn`

我正在使用两个不同的库(特别是napi-rscallback-future ),并希望在另一个库的Fn函数中调用一个库中的FnOnce函数。 具体来说,我正在尝试向 JavaScript 公开一个 Rust 函数,该函数在调用时完成一个Future

由于暴露的 JS 函数在技术上可以随时被捕获和调用,Rust 无法保证函数只会被调用一次,所以它不得不假设函数会被调用多次。 但是, callback-future要求Future只完成一次(通过调用FnOnce )。 我无法修改这两个签名,它们对于各自的用例都是准确的。 我怎样才能让两者一起工作,以便我可以在Fn回调中解析Future

我知道多次调用FnOnce是不行的,我可以使用unsafe的代码或在运行时强制执行该函数只调用一次。 在调用FnOnce之前,可以检测并拒绝后续的调用尝试,但我不确定如何与 Rust 编译器沟通我正在这样做并且允许调用FnOnce是可以的。 目前我拥有的是:

// Create a `CallbackFuture` to wait for JS to respond.
// `complete` is an `FnOnce` to mark the `Future` as "Ready".
CallbackFuture::<Result<String, Error>>::new(move |complete| {
  thread_safe_js_function.call(Ok(Args {
    // Other arguments...

    // Callback for JS to invoke when done.
    // `Fn` because JS could theoretically call this multiple times,
    // though that shouldn't be allowed anyways.
    callback: Box::new(move |ctx| {
      let result = ctx.get::<JsString>(0)?.into_utf8()?.as_str()?.to_owned();

      // Complete the `Future` with the result.
      complete(Ok(result));

      ctx.env.get_undefined() // Return `undefined` to JS.
    }),
  }), ThreadsafeFunctionCallMode::Blocking);
}).await

这给了我错误:

error[E0507]: cannot move out of `complete`, a captured variable in an `Fn` closure
   --> node/src/lib.rs:368:15
    |
352 |           CallbackFuture::<Result<PathBuf, Error<BundleErrorKind>>>::new(move |complete| {
    |                                                                                -------- captured outer variable
...
358 |               callback: Box::new(move |ctx| {
    |  ________________________________-
...   |
368 | |               complete(Ok(result));
    | |               ^^^^^^^^ move occurs because `complete` has type `std::boxed::Box<dyn FnOnce(Result<PathBuf, Error>) + Send>`, which does not implement the `Copy` trait
369 | |     
370 | |               ctx.env.get_undefined()
371 | |             }),
    | |_____________- captured by this `Fn` closure

For more information about this error, try `rustc --explain E0507`.

虽然错误抱怨在闭包之间移动,但我的理解是这是不允许的,因为completeFnOnce并且我试图从Fn调用它。 如果有另一种方法可以解决关闭问题,那么我想这也可能是一个可行的解决方案。

此外,如果 N-API 中有一种方法可以接受Promise结果并await它而不是通过回调,那也可能是一个很好的选择。 我相信您可以在 Rust 中await Promise ,但是 AFAICT 无法从线程安全的 N-API 函数接收同步结果,因为napi-rs似乎忽略了返回值

到目前为止,我找到的唯一解决方案是分叉callback-future API 以将complete FnOnceFn ,这显然不是一个很好的解决方案。 我还尝试了从FnOnceFnstd::mem::transmute() ,认为只要我只调用一次该函数,以这种方式强制转换就可以工作。 但是,在调用时这样做会出现段错误,所以我认为它不会按照我想要的方式工作。 非常感谢这里的任何想法!

由于您没有我们可以自己编译的示例并且缺少一些细节,因此我将解决您问题的核心:您如何从Fn调用FnOnce

您已经知道的第一个问题:如果您尝试直接调用FnOnce ,这是不允许的,因为它会消耗值,这会使调用闭包本身FnOnce ,但您需要一个Fn

第二个是,如果您尝试将Option之类的东西与它的take()方法一起使用,您会发现Fn无法改变其捕获的状态(它必须是FnMut才能做到这一点)。

解决方案是将Option包装在提供内部可变性的类型中。 根据您是否需要Fn也成为Send + Sync ,您可以使用CellMutex

使用不是Send + SyncCell ,它看起来像这样:

use std::cell::Cell;

fn print_owned(x: String) {
    println!("print_owned called with {:?}", x);
}

fn call_twice(f: impl Fn()) {
    f();
    f();
}

fn main() {
    let foo = "foo".to_string();
    let fnonce = move || print_owned(foo);
    
    let maybe_fnonce = Cell::new(Some(fnonce));
    
    call_twice(move || {
        match maybe_fnonce.take() {
            Some(inner) => inner(),
            None => println!("maybe_fnonce is None"),
        }
    });
}

当传递给call_twice()的闭包在Cell上调用take()时,内部值被提取并替换为None 如果再次调用该函数,则内部值将是先前放在那里的None 这也意味着您可以检测到这种情况,并可能将问题发回 JavaScript 端。

如果闭包需要Send + Sync ,那么您可以改用Mutex<Option<_>>

use std::sync::Mutex;

fn print_owned(x: String) {
    println!("print_owned called with {:?}", x);
}

fn call_twice(f: impl Fn()) {
    f();
    f();
}

fn main() {
    let foo = "foo".to_string();
    let fnonce = move || print_owned(foo);
    
    let maybe_fnonce = Mutex::new(Some(fnonce));
    
    call_twice(move || {
        match maybe_fnonce.lock().unwrap().take() {
            Some(inner) => inner(),
            None => println!("maybe_fnonce is None"),
        }
    });
}

您需要做的就是将此技术应用于您的具体情况。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM