我正在使用两个不同的库(特别是 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!
发布评论
评论(1)
由于您没有示例,我们可以自行编译,并且有一些缺少的细节,我将解决您的问题的核心:如何从
fn fnonce
>?您已经知道的第一个问题是:如果您尝试直接调用
fnonce
,这是不允许的,因为它消耗了该值,这将使调用封闭本身fnonce
,但是您需要fn
。第二个是,如果您尝试使用
option
take()
方法之类的东西,您会发现fn
无法突变其捕获的状态(必须是fnmut
才能执行此操作)。解决方案是将
选项
包裹在提供内部变异性的类型中。取决于您是否需要fn
也为send + sync
,您可以使用cell
或mutex
。使用
单元格
,它是不是send + sync
,它看起来像这样:当闭合传递到
call_twice()< /code> indokes
。这也意味着您可以检测到这种情况,并可能向JavaScript侧发出问题。take()
在单元格
上,提取内值并用none
替换。如果再次调用该函数,则内部值将为none
先前放置的如果封闭需要
发送 +同步
,则可以使用mutex&lt; option&lt; _&gt;&gt;&gt;
:您所要做的就是将此技术应用于您的特定情况。
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 anFn
?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 itselfFnOnce
, but you need anFn
.The second is that if you try to use something like
Option
with itstake()
method, you'll find thatFn
can't mutate its captured state (it would have to beFnMut
to do that).The solution is to wrap an
Option
in a type providing interior mutability. Depending on whether you need yourFn
to also beSend + Sync
, you could use eitherCell
orMutex
.With
Cell
, which is notSend + Sync
, it would look something like this:When the closure passed to
call_twice()
invokestake()
on theCell
, the inner value is extracted and replaced withNone
. If the function is called again, the inner value will be theNone
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 useMutex<Option<_>>
:All you need to do is apply this technique to your specific situation.