简体   繁体   English

从`Fn`调用`FnOnce`

[英]Call `FnOnce` from `Fn`

I'm working with two different libraries (specifically napi-rs and callback-future ) and want to invoke a FnOnce function from one library in a Fn function from another.我正在使用两个不同的库(特别是napi-rscallback-future ),并希望在另一个库的Fn函数中调用一个库中的FnOnce函数。 Specifically, I'm trying to expose a Rust function to JavaScript which completes a Future when invoked.具体来说,我正在尝试向 JavaScript 公开一个 Rust 函数,该函数在调用时完成一个Future

Since the exposed JS function can technically be captured and invoked at any time, there is no way for Rust to guarantee that the function will only be called once, so it has to assume that the function will be called many times.由于暴露的 JS 函数在技术上可以随时被捕获和调用,Rust 无法保证函数只会被调用一次,所以它不得不假设函数会被调用多次。 However, callback-future requires that a Future is only completed once (via invoking an FnOnce ).但是, callback-future要求Future只完成一次(通过调用FnOnce )。 I can't modify these two signatures, and they are both accurate for their respective use cases.我无法修改这两个签名,它们对于各自的用例都是准确的。 How can I get the two to work together so I can resolve a Future in a Fn callback?我怎样才能让两者一起工作,以便我可以在Fn回调中解析Future

I understand that it's not ok to invoke the FnOnce multiple times, and I'm ok with using unsafe code or otherwise enforcing at runtime that the function is only called once.我知道多次调用FnOnce是不行的,我可以使用unsafe的代码或在运行时强制执行该函数只调用一次。 Subsequent invocation attempts can be detected and rejected before calling the FnOnce , but I'm not sure how to communicate to the Rust compiler that I'm doing this and that it's ok to allow the call to FnOnce .在调用FnOnce之前,可以检测并拒绝后续的调用尝试,但我不确定如何与 Rust 编译器沟通我正在这样做并且允许调用FnOnce是可以的。 Currently what I have is:目前我拥有的是:

// 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

This gives me the error:这给了我错误:

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`.

While the error is complaining about moving between closures, my understanding is that this isn't allowed because complete is FnOnce and I'm trying to call it from an Fn .虽然错误抱怨在闭包之间移动,但我的理解是这是不允许的,因为completeFnOnce并且我试图从Fn调用它。 If there's another approach which solves the closure issue, then I guess that could be a viable solution too.如果有另一种方法可以解决关闭问题,那么我想这也可能是一个可行的解决方案。

Also if there's a way in N-API to accept a Promise result and await it instead of going through a callback, that could be a great alternative as well.此外,如果 N-API 中有一种方法可以接受Promise结果并await它而不是通过回调,那也可能是一个很好的选择。 I believe you can await a Promise in Rust, but AFAICT there's no way to receive a synchronous result from a threadsafe N-API function as napi-rs seems to ignore the return value .我相信您可以在 Rust 中await Promise ,但是 AFAICT 无法从线程安全的 N-API 函数接收同步结果,因为napi-rs似乎忽略了返回值

So far, the only solution I've found is to fork the callback-future API to change complete from an FnOnce to an Fn , which obviously isn't a great solution.到目前为止,我找到的唯一解决方案是分叉callback-future API 以将complete FnOnceFn ,这显然不是一个很好的解决方案。 I also tried std::mem::transmute() from FnOnce to Fn thinking that forcing a cast that way would work as long as I only called the function once.我还尝试了从FnOnceFnstd::mem::transmute() ,认为只要我只调用一次该函数,以这种方式强制转换就可以工作。 However doing so segfaulted when invoked, so I don't think it works the way I want it to here.但是,在调用时这样做会出现段错误,所以我认为它不会按照我想要的方式工作。 Any ideas here are greatly appreciated!非常感谢这里的任何想法!

Since you don't have an example we can compile ourselves and there is some missing detail, I will address the heart of your question: how do you call an FnOnce from an Fn ?由于您没有我们可以自己编译的示例并且缺少一些细节,因此我将解决您问题的核心:您如何从Fn调用FnOnce

The first problem you already know: if you try to call the FnOnce directly, this is disallowed because it consumes the value, which would make the calling closure itself FnOnce , but you need an Fn .您已经知道的第一个问题:如果您尝试直接调用FnOnce ,这是不允许的,因为它会消耗值,这会使调用闭包本身FnOnce ,但您需要一个Fn

The second is that if you try to use something like Option with its take() method, you'll find that Fn can't mutate its captured state (it would have to be FnMut to do that).第二个是,如果您尝试将Option之类的东西与它的take()方法一起使用,您会发现Fn无法改变其捕获的状态(它必须是FnMut才能做到这一点)。

The solution is to wrap an Option in a type providing interior mutability.解决方案是将Option包装在提供内部可变性的类型中。 Depending on whether you need your Fn to also be Send + Sync , you could use either Cell or Mutex .根据您是否需要Fn也成为Send + Sync ,您可以使用CellMutex

With Cell , which is not Send + Sync , it would look something like this:使用不是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"),
        }
    });
}

When the closure passed to call_twice() invokes take() on the Cell , the inner value is extracted and replaced with None .当传递给call_twice()的闭包在Cell上调用take()时,内部值被提取并替换为None If the function is called again, the inner value will be the None previously put there.如果再次调用该函数,则内部值将是先前放在那里的None This also means you can detect this situation and possibly signal the problem back to the JavaScript side.这也意味着您可以检测到这种情况,并可能将问题发回 JavaScript 端。

If the closure needs to be Send + Sync then you can instead use Mutex<Option<_>> :如果闭包需要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"),
        }
    });
}

All you need to do is apply this technique to your specific situation.您需要做的就是将此技术应用于您的具体情况。

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

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