从 `Fn` 调用 `FnOnce`
Call `FnOnce` from `Fn`
我正在使用两个不同的库(特别是 napi-rs
and callback-future
)并想在另一个库的 Fn
函数中调用一个库的 FnOnce
函数。具体来说,我试图将 Rust 函数公开给 JavaScript,它在调用时完成 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`.
虽然错误是抱怨在闭包之间移动,但我的理解是这是不允许的,因为 complete
是 FnOnce
并且我试图从 Fn
.如果有另一种方法可以解决关闭问题,那么我想这也是一个可行的解决方案。
此外,如果 N-API 中有一种方法可以接受 Promise
结果并 await
它而不是通过回调,那也是一个很好的选择。我相信你可以在 Rust 中 await
a Promise
,但是 AFAICT 没有办法从线程安全 N-API 函数接收同步结果,因为 napi-rs
似乎 ignore the return value.
到目前为止,我找到的唯一解决方案是分叉 callback-future
API 以将 complete
从 FnOnce
更改为 Fn
,这显然不是一个很好的解决方案。我还尝试了从 FnOnce
到 Fn
的 std::mem::transmute()
,认为只要我只调用函数一次,就可以强制强制转换。但是,在调用时这样做会出现段错误,所以我认为它不会按照我希望的方式工作。非常感谢这里的任何想法!
由于您没有我们可以自己编译的示例并且缺少一些细节,我将解决您问题的核心:如何从 Fn
调用 FnOnce
?
您已经知道的第一个问题:如果您尝试直接调用 FnOnce
,这是不允许的,因为它会消耗值,这会使调用闭包本身 FnOnce
,但您需要一个 Fn
.
第二个是,如果您尝试将 Option
之类的东西与其 take()
方法一起使用,您会发现 Fn
无法改变其捕获状态(它会必须 FnMut
才能做到这一点)。
解决方案是将 Option
包装在提供内部可变性的类型中。根据您是否需要 Fn
也是 Send + Sync
,您可以使用 Cell
或 Mutex
。
使用 Cell
,即 而不是 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()
的闭包在 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"),
}
});
}
您需要做的就是将此技术应用到您的具体情况。
我正在使用两个不同的库(特别是 napi-rs
and callback-future
)并想在另一个库的 Fn
函数中调用一个库的 FnOnce
函数。具体来说,我试图将 Rust 函数公开给 JavaScript,它在调用时完成 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`.
虽然错误是抱怨在闭包之间移动,但我的理解是这是不允许的,因为 complete
是 FnOnce
并且我试图从 Fn
.如果有另一种方法可以解决关闭问题,那么我想这也是一个可行的解决方案。
此外,如果 N-API 中有一种方法可以接受 Promise
结果并 await
它而不是通过回调,那也是一个很好的选择。我相信你可以在 Rust 中 await
a Promise
,但是 AFAICT 没有办法从线程安全 N-API 函数接收同步结果,因为 napi-rs
似乎 ignore the return value.
到目前为止,我找到的唯一解决方案是分叉 callback-future
API 以将 complete
从 FnOnce
更改为 Fn
,这显然不是一个很好的解决方案。我还尝试了从 FnOnce
到 Fn
的 std::mem::transmute()
,认为只要我只调用函数一次,就可以强制强制转换。但是,在调用时这样做会出现段错误,所以我认为它不会按照我希望的方式工作。非常感谢这里的任何想法!
由于您没有我们可以自己编译的示例并且缺少一些细节,我将解决您问题的核心:如何从 Fn
调用 FnOnce
?
您已经知道的第一个问题:如果您尝试直接调用 FnOnce
,这是不允许的,因为它会消耗值,这会使调用闭包本身 FnOnce
,但您需要一个 Fn
.
第二个是,如果您尝试将 Option
之类的东西与其 take()
方法一起使用,您会发现 Fn
无法改变其捕获状态(它会必须 FnMut
才能做到这一点)。
解决方案是将 Option
包装在提供内部可变性的类型中。根据您是否需要 Fn
也是 Send + Sync
,您可以使用 Cell
或 Mutex
。
使用 Cell
,即 而不是 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()
的闭包在 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"),
}
});
}
您需要做的就是将此技术应用到您的具体情况。