从`fn'呼叫`fnonce`

发布于 2025-02-04 10:03:15 字数 3429 浏览 2 评论 0 原文

我正在使用两个不同的库(特别是 napi-rs chardback-future ),并希望调用 fnonce 来自 fn 函数的一个库的功能来自另一个库。具体来说,我正在尝试将Rust功能暴露于JavaScript,该JavaScript在调用时完成了 Future

由于在技术上可以随时捕获和调用暴露的JS功能,因此无法确保Rust只能调用一次功能,因此必须假设该函数将被调用多次。但是,回调future 要求未来仅完成一次(通过调用 fnonce )。我无法修改这两个签名,它们都适合各自用例。我如何让两者一起工作,以便可以在 fn 回调中解析 Future

我知道多次调用 fnonce ,我可以使用 UNSFAFE 代码,或者在运行时强制执行该函数仅调用一次。在调用 fnonce 之前,可以检测和拒绝随后的调用尝试,但是我不确定如何与Rust Compiler通信我正在执行此操作,并且可以允许呼叫 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`.

虽然错误在抱怨关闭之间移动,但我的理解是不允许这样做,因为完整 fnonce 和我正在尝试从 fn 调用它。如果还有另一种解决封闭问题的方法,那么我想这也可能是一个可行的解决方案。

同样,如果N-API有一种方法可以接受 Promise 结果和等待它而不是通过回调,那也可能是一个不错的选择。我相信您可以等待 A Promise 在Rust中,但是AFAICT无法从threadsafe n-api函数中接收同步结果,因为 napi-rs seems to ignore the返回值

到目前为止,我发现的唯一解决方案是将回调 api分配到完整 fnonce 更改为 FN ,这显然不是一个很好的解决方案。我还尝试了 std :: mem :: transmute() fnonce to fn 认为,只要我强迫这样的施法就可以工作仅调用一次函数。但是在调用时这样做的是segfault,所以我认为它不会像我希望在这里那样有效。这里的任何想法都非常感谢!

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. Specifically, I'm trying to expose a Rust function to JavaScript which completes a Future when invoked.

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. However, callback-future requires that a Future is only completed once (via invoking an 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?

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

倾城花音 2025-02-11 10:03:15

由于您没有示例,我们可以自行编译,并且有一些缺少的细节,我将解决您的问题的核心:如何从 fn fnonce >?

您已经知道的第一个问题是:如果您尝试直接调用 fnonce ,这是不允许的,因为它消耗了该值,这将使调用封闭本身 fnonce ,但是您需要 fn

第二个是,如果您尝试使用 option take()方法之类的东西,您会发现 fn 无法突变其捕获的状态(必须是 fnmut 才能执行此操作)。

解决方案是将选项包裹在提供内部变异性的类型中。取决于您是否需要 fn 也为 send + sync ,您可以使用 cell mutex

使用单元格,它是不是 send + sync ,它看起来像这样:

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()< /code> indokes take()单元格上,提取内值并用 none 替换。如果再次调用该函数,则内部值将为 none 先前放置的。这也意味着您可以检测到这种情况,并可能向JavaScript侧发出问题。

如果封闭需要发送 +同步,则可以使用 mutex&lt; option&lt; _&gt;&gt;&gt;

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"),
        }
    });
}

您所要做的就是将此技术应用于您的特定情况。

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?

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.

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

The solution is to wrap an Option in a type providing interior mutability. Depending on whether you need your Fn to also be Send + Sync, you could use either Cell or Mutex.

With Cell, which is not Send + Sync, it would look something like this:

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. If the function is called again, the inner value will be the None previously put there. This also means you can detect this situation and possibly signal the problem back to the JavaScript side.

If the closure needs to be Send + Sync then you can instead use 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文