Rust Wasm使用FileReader报E0525闭包trait不匹配错误如何修复
问题现象
编写基于web-sys的前端文件读取逻辑时,move |event: Event|闭包位置触发编译错误,问题代码如下:
pub struct FileStream{ el : HtmlInputElement, } impl FileStream { pub fn new(el : HtmlInputElement) -> FileStream { FileStream {el} } pub fn get_file(&self){ let file = self.el.files().unwrap().get(0).unwrap(); let file_reader = FileReader::new().unwrap(); file_reader.read_as_data_url(&file).unwrap(); let onloadend = Closure::wrap(Box::new(move |event: Event| { let file_reader = file_reader.unchecked_into::<web_sys::FileReader>(); let data_url = file_reader.result().unwrap(); let data_url = data_url.unchecked_into::<JsValue>(); let data_url = data_url.as_string().unwrap(); let audio_el = self.el.unchecked_into::<HtmlAudioElement>(); audio_el.set_src(&data_url); }) as Box<dyn FnMut(_)>); file_reader.set_onloadend(Some(onloadend.as_ref().unchecked_ref())); } }
编译输出错误信息:
error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce` --> src/lib.rs:29:48 | 29 | let onloadend = Closure::wrap(Box::new(move |event: Event| { | - ^^^^^^^^^^^^^^^^^^^ this closure implements `FnOnce`, not `FnMut` | _______________________________________| | | 30 | | let file_reader = file_reader.unchecked_into::<web_sys::FileReader>(); | | ----------- closure is `FnOnce` because it moves the variable `file_reader` out of its environment 31 | | let data_url = file_reader.result().unwrap(); 32 | | let data_url = data_url.unchecked_into::<JsValue>(); ... | 35 | | audio_el.set_src(&data_url); 36 | | }) as Box<dyn FnMut(_)>); | |__________- the requirement to implement `FnMut` derives from here For more information about this error, try `rustc --explain E0525`.
错误原因
- 绑定给DOM事件的回调要求实现
FnMuttrait,支持被多次调用,但当前闭包第一次执行时,就会把捕获到的外层file_reader变量move进作用域消耗掉,后续再次触发回调时没有可用的file_reader变量,因此闭包只实现了FnOnce,不符合trait约束。 - 代码还存在两个隐藏问题:一是直接在闭包中捕获
&self引用,和闭包需要的静态生命周期要求不匹配;二是创建的onloadend闭包在get_file函数执行结束后就会被Rust回收,会导致回调触发时出现内存访问错误。
修复方法
- 不要在闭包中捕获外层的
file_reader变量,事件触发时直接从传入的event参数中取事件目标,也就是当前触发loadend事件的FileReader实例。 - 提前clone需要用到的DOM元素,避免直接捕获self引用。
- 对创建的Closure调用
forget()方法,将其生命周期泄漏给JS环境,保证回调触发时闭包没有被提前回收。
修复后的可运行代码:
use wasm_bindgen::prelude::*; use web_sys::{Event, FileReader, HtmlAudioElement, HtmlInputElement}; pub struct FileStream{ el : HtmlInputElement, } impl FileStream { pub fn new(el : HtmlInputElement) -> FileStream { FileStream {el} } pub fn get_file(&self){ let file = self.el.files().unwrap().get(0).unwrap(); let file_reader = FileReader::new().unwrap(); file_reader.read_as_data_url(&file).unwrap(); // 提前clone需要的DOM元素,避免捕获self引用 let input_el = self.el.clone(); let onloadend = Closure::wrap(Box::new(move |event: Event| { // 直接从事件对象获取触发事件的FileReader,无需捕获外层变量 let file_reader: FileReader = event.target().unwrap().unchecked_into(); let data_url = file_reader.result().unwrap(); let data_url = data_url.as_string().unwrap(); let audio_el: HtmlAudioElement = input_el.unchecked_into(); audio_el.set_src(&data_url); }) as Box<dyn FnMut(_)>); file_reader.set_onloadend(Some(onloadend.as_ref().unchecked_ref())); // 泄漏闭包生命周期给JS环境,避免被提前回收 onloadend.forget(); } }
内容的提问来源于stack exchange,提问作者jerome leong
相关产品推荐
相关产品推荐

