[英]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-rs
和callback-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
.虽然错误抱怨在闭包之间移动,但我的理解是这是不允许的,因为
complete
是FnOnce
并且我试图从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
FnOnce
为Fn
,这显然不是一个很好的解决方案。 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.我还尝试了从
FnOnce
到Fn
的std::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
,您可以使用Cell
或Mutex
。
With Cell
, which is not Send + Sync
, it would look something like this:使用不是
Send + Sync
的Cell
,它看起来像这样:
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.