首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何使用Rust和wasm-bindgen创建一个闭包来创建另一个带有state的闭包?

如何使用Rust和wasm-bindgen创建一个闭包来创建另一个带有state的闭包?
EN

Stack Overflow用户
提问于 2020-08-13 02:02:45
回答 1查看 310关注 0票数 1

我正在尝试创建一个小的web应用程序,将允许用户拖放文件到窗口。然后将读取这些文件,并将其内容和文件名一起打印到控制台。此外,这些文件将被添加到列表中。

JS中的等效代码可能如下所示:

代码语言:javascript
复制
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移出它的作用域,这破坏了Closure::wrap的预期签名。Closure::once不会这样做,因为我需要能够将多个文件拖放到窗口上。

下面是我尝试过但没有成功的代码:

代码语言:javascript
复制
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();
}

我得到的错误是

代码语言:javascript
复制
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 {:?}", name, 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.

在一个更复杂的示例中,我无法以更简单的形式重现,我得到了一个更隐蔽的错误,但我希望它与上面的内容相关:

代码语言:javascript
复制
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,)>` closure, found `[closure@src/main.rs:621:52: 649:10 contents:std::option::Option<std::string::String>, drop_proxy:winit::event_loop::EventLoopProxy<CustomEvent>]`

我尝试将all_files变量放入RefCell中,但仍然收到类似的错误。在Rust中有什么技巧或类型可以用来解决这个问题并实现我想要的吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-08-13 03:09:57

首先,您正在尝试将name复制到许多File实例中,但必须对其进行克隆。其次,您需要适当地确保无论何时闭包想要调用all_files都可以使用它。要做到这一点,一种方法是使用RefCell来支持多个闭包对其进行写操作,并将其包装在Rc中,以确保只要有任何闭包处于活动状态,它就会保持活动状态。

试试这个:

代码语言:javascript
复制
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()

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63382333

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档