如何解决如何将Rust与wasm-bindgen结合使用,以创建一个具有状态的另一个闭包?
我正在尝试创建一个小型Web应用程序,该应用程序将允许用户将文件拖放到窗口上。然后将读取文件,并将其内容和文件名一起打印到控制台。另外,这些文件将被添加到列表中。
JS中的等效代码可能类似于:
window.ondragenter = (e) => {
e.preventDefault();
}
window.ondragover = (e) => {
e.preventDefault();
}
const allFiles = [];
const dropCallback = (e) => {
e.preventDefault();
const files = e.dataTransfer.files;
console.log("Got",files.length,"files");
for (let i = 0; i < files.length; i++) {
const file = files.item(i);
const fileName = file.name;
const readCallback = (text) => {
console.log(fileName,text);
allFiles.push({fileName,text});
}
file.text().then(readCallback);
}
};
window.ondrop = dropCallback;
当尝试在Rust中执行此操作时,我遇到一个问题,即外部闭包需要实现FnOnce
才能再次将all_files
移出其作用域,这会破坏{{ 1}}。 Closure::wrap
不会解决问题,因为我需要能够将多个文件拖放到窗口上。
这是我没有运气尝试过的代码:
Closure::once
我得到的错误是
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
macro_rules! console_log {
($($t:tt)*) => (web_sys::console::log_1(&JsValue::from(format_args!($($t)*).to_string())))
}
struct File {
name: String,contents: String,}
#[wasm_bindgen]
pub fn main() {
let mut all_files = Vec::new();
let drop_callback = Closure::wrap(Box::new(move |event: &web_sys::Event| {
event.prevent_default();
let drag_event_ref: &web_sys::DragEvent = JsCast::unchecked_from_js_ref(event);
let drag_event = drag_event_ref.clone();
match drag_event.data_transfer() {
None => {}
Some(data_transfer) => match data_transfer.files() {
None => {}
Some(files) => {
console_log!("Got {:?} files",files.length());
for i in 0..files.length() {
if let Some(file) = files.item(i) {
let name = file.name();
let read_callback = Closure::wrap(Box::new(move |text: JsValue| {
let contents = text.as_string().unwrap();
console_log!("Contents of {:?} are {:?}",name,contents);
all_files.push(File {
name,contents
});
}) as Box<dyn FnMut(JsValue)>);
file.text().then(&read_callback);
read_callback.forget();
}
}
}
},}
}) as Box<dyn FnMut(&web_sys::Event)>);
// These are just necessary to make sure the drop event is sent
let drag_enter = Closure::wrap(Box::new(|event: &web_sys::Event| {
event.prevent_default();
console_log!("Drag enter!");
}) as Box<dyn FnMut(&web_sys::Event)>);
let drag_over = Closure::wrap(Box::new(|event: &web_sys::Event| {
event.prevent_default();
console_log!("Drag over!");
}) as Box<dyn FnMut(&web_sys::Event)>);
// Register all the events on the window
web_sys::window()
.and_then(|win| {
win.set_ondragenter(Some(JsCast::unchecked_from_js_ref(drag_enter.as_ref())));
win.set_ondragover(Some(JsCast::unchecked_from_js_ref(drag_over.as_ref())));
win.set_ondrop(Some(JsCast::unchecked_from_js_ref(drop_callback.as_ref())));
win.document()
})
.expect("Could not find window");
// Make sure our closures outlive this function
drag_enter.forget();
drag_over.forget();
drop_callback.forget();
}
在一个我无法以更简单的形式复制的更复杂的示例中,我得到了一个更加神秘的错误,但我希望它与以上内容有关:
error[E0525]: expected a closure that implements the `FnMut` trait,but this closure only implements `FnOnce`
--> src/lib.rs:33:72
|
33 | ... let read_callback = Closure::wrap(Box::new(move |text: JsValue| {
| - ^^^^^^^^^^^^^^^^^^^^ this closure implements `FnOnce`,not `FnMut`
| _________________________________________________________|
| |
34 | | ... let contents = text.as_string().unwrap();
35 | | ... console_log!("Contents of {:?} are {:?}",contents);
36 | | ...
37 | | ... all_files.push(File {
38 | | ... name,| | ---- closure is `FnOnce` because it moves the variable `name` out of its environment
39 | | ... contents
40 | | ... });
41 | | ... }) as Box<dyn FnMut(JsValue)>);
| |________________________- the requirement to implement `FnMut` derives from here
error[E0525]: expected a closure that implements the `FnMut` trait,but this closure only implements `FnOnce`
--> src/lib.rs:20:48
|
20 | let drop_callback = Closure::wrap(Box::new(move |event: &web_sys::Event| {
| - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this closure implements `FnOnce`,not `FnMut`
| _______________________________________|
| |
21 | | event.prevent_default();
22 | | let drag_event_ref: &web_sys::DragEvent = JsCast::unchecked_from_js_ref(event);
23 | | let drag_event = drag_event_ref.clone();
... |
33 | | let read_callback = Closure::wrap(Box::new(move |text: JsValue| {
| | -------------------- closure is `FnOnce` because it moves the variable `all_files` out of its environment
... |
50 | | }
51 | | }) as Box<dyn FnMut(&web_sys::Event)>);
| |______- the requirement to implement `FnMut` derives from here
error: aborting due to 2 previous errors; 1 warning emitted
For more information about this error,try `rustc --explain E0525`.
error: could not compile `hello_world`.
To learn more,run the command again with --verbose.
我尝试将error[E0277]: expected a `std::ops::FnMut<(&web_sys::Event,)>` closure,found `[closure@src/main.rs:621:52: 649:10 contents:std::option::Option<std::string::String>,drop_proxy:winit::event_loop::EventLoopProxy<CustomEvent>]`
--> src/main.rs:621:43
|
621 | let drop_callback = Closure::wrap(Box::new(move |event: &web_sys::Event| {
| ___________________________________________^
622 | | event.prevent_default();
623 | | let drag_event_ref: &web_sys::DragEvent = JsCast::unchecked_from_js_ref(event);
624 | | let drag_event = drag_event_ref.clone();
... |
648 | | }
649 | | }) as Box<dyn FnMut(&web_sys::Event)>);
| |__________^ expected an `FnMut<(&web_sys::Event,drop_proxy:winit::event_loop::EventLoopProxy<CustomEvent>]`
变量放入all_files
中,但是仍然出现类似的错误。在Rust中有什么技巧或类型可以用来解决此问题并实现我想要的吗?
解决方法
首先,您尝试将name
复制到File
的多个实例中,但是必须将其克隆。其次,您需要适当地确保all_files
在闭包想要调用它时将可用。一种方法是使用RefCell
启用多个闭包对其进行写入,并将其包装在Rc
中以确保只要任何闭包都处于活动状态,它就保持活动状态。 / p>
尝试一下:
use std::{cell::RefCell,rc::Rc};
use wasm_bindgen::{prelude::*,JsCast,JsValue};
macro_rules! console_log {
($($t:tt)*) => (web_sys::console::log_1(&JsValue::from(format_args!($($t)*).to_string())))
}
struct File {
name: String,contents: String,}
#[wasm_bindgen]
pub fn main() {
let all_files = Rc::new(RefCell::new(Vec::new()));
let drop_callback = Closure::wrap(Box::new(move |event: &web_sys::Event| {
event.prevent_default();
let drag_event_ref: &web_sys::DragEvent = event.unchecked_ref();
let drag_event = drag_event_ref.clone();
match drag_event.data_transfer() {
None => {}
Some(data_transfer) => match data_transfer.files() {
None => {}
Some(files) => {
console_log!("Got {:?} files",files.length());
for i in 0..files.length() {
if let Some(file) = files.item(i) {
let name = file.name();
let all_files_ref = Rc::clone(&all_files);
let read_callback = Closure::wrap(Box::new(move |text: JsValue| {
let contents = text.as_string().unwrap();
console_log!("Contents of {:?} are {:?}",&name,contents);
(*all_files_ref).borrow_mut().push(File {
name: name.clone(),contents,});
})
as Box<dyn FnMut(JsValue)>);
file.text().then(&read_callback);
read_callback.forget();
}
}
}
},}
}) as Box<dyn FnMut(&web_sys::Event)>);
// These are just necessary to make sure the drop event is sent
let drag_enter = Closure::wrap(Box::new(|event: &web_sys::Event| {
event.prevent_default();
console_log!("Drag enter!");
}) as Box<dyn FnMut(&web_sys::Event)>);
let drag_over = Closure::wrap(Box::new(|event: &web_sys::Event| {
event.prevent_default();
console_log!("Drag over!");
}) as Box<dyn FnMut(&web_sys::Event)>);
// Register all the events on the window
web_sys::window()
.and_then(|win| {
win.set_ondragenter(Some(drag_enter.as_ref().unchecked_ref()));
win.set_ondragover(Some(drag_over.as_ref().unchecked_ref()));
win.set_ondrop(Some(drop_callback.as_ref().unchecked_ref()));
win.document()
})
.expect("Could not find window");
// Make sure our closures outlive this function
drag_enter.forget();
drag_over.forget();
drop_callback.forget();
}
请注意,如果您正在使用多个线程,则可能需要除RefCell
之外的其他内容(可能是Mutex
)。另外,我还将JsCast::unchecked_from_js_ref(x)
的用法更改为更规范的x.as_ref().unchecked_ref()
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。