I was mentally compliing this issue before it got solved so I'm adding it for reference.
I had a long callback, inside a closure, that was distracting me from the main logic. So I wanted to move it outside the closure/calling-function entirely.
pub fn data_import_stage_1(model: &Model, args: &Args) {
let reader = FileReader::new().expect("Failed to create FileReader");
// snip....
let mut model_copy = model.clone();
let mut args_copy = args.clone();
let reader_copy = reader.clone();
==============================
let mut PROCESS_IMPORT_INSIDE = move || {
// snip....
let json = &reader_copy
// snip....
args_copy.entries.insert(/* ... */);
controller::stg_data_import(2, &mut model_copy, &mut args_copy);
};
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ VERY LONG CALLBACK I WANT TO MOVE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
let reader_event = move |event: ProgressEvent| {
// snip....
match event.type_().as_str() {
// snip....
"loadend" => {
// snip....
PROCESS_IMPORT_INSIDE();
}
}
};
let add_event_listeners = || {
let closure = Closure::wrap(Box::new(reader_event) as Box<dyn FnMut(ProgressEvent)>);
let closure_ref = closure.as_ref().unchecked_ref();
// snip....
reader.set_onloadend(Some(closure_ref));
closure.forget();
};
if let Some(files) = files {
if let Some(file) = files.get(0) {
// snip....
add_event_listeners();
reader.read_as_text(&file).expect("Ok: `()`");
}
}
}
This is how it started:
fn PROCESS_IMPORT_OUTSIDE(mut model_copy: Model, mut args_copy: Args, reader_copy: FileReader) {
// snip....
let json = &reader_copy
// snip....
args_copy.entries.insert(/* ... */);
controller::stg_data_import(2, &mut model_copy, &mut args_copy);
}
pub fn data_import_stage_1(model: &Model, args: &Args) {
let reader = FileReader::new().expect("Failed to create FileReader");
let mut model_copy = model.clone();
let mut args_copy = args.clone();
let reader_copy = reader.clone();
let reader_event = move |event: ProgressEvent| {
// snip....
match event.type_().as_str() {
// snip....
"loadend" => {
// snip....
PROCESS_IMPORT_OUTSIDE(model_copy, args_copy, reader_copy);
}
}
};
let add_event_listeners = || {
let closure = Closure::wrap(Box::new(reader_event) as Box<dyn FnMut(ProgressEvent)>);
let closure_ref = closure.as_ref().unchecked_ref();
// snip....
reader.set_onloadend(Some(closure_ref));
closure.forget();
};
if let Some(files) = files {
if let Some(file) = files.get(0) {
// snip....
add_event_listeners();
reader.read_as_text(&file).expect("Ok: `()`");
}
}
}
But then I got this, which sent me down a rabbit hole of setting the Box
to dyn FnOnce
--> src/view/settings/create.rs:68:24
|
68 | ...t reader_event = move |event: ProgressEvent| {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ this closure implements `FnOnce`, not `FnMut`
...
81 | ... process_import(model_copy, args_copy, reader_copy);
| ---------- closure is `FnOnce` because it moves the variable `model_copy` out of its environment
...
88 | ... let closure = Closure::wrap(Box::new(reader_event) as Box<dyn FnMut(Progress...
| ---------------------- the requirement to implement `FnMut` derives from here
|
= note: required for the cast from `Box<{closure@src/view/settings/create.rs:68:24: 68:51}>` to `Box<dyn FnMut(ProgressEvent)>`
Which made things worse for me when trying to get FnOnce
to work...
error[E0277]: the trait bound `dyn FnOnce(ProgressEvent): WasmClosure` is not satisfied
--> src/view/settings/create.rs:88:37
|
88 | ... = Closure::wrap(Box::new(reader_event) as Box<dyn FnOnce(ProgressEvent)>);
| ------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WasmClosure` is not implemented for `dyn FnOnce(ProgressEvent)`
| |
| required by a bound introduced by this call
|
= help: the following other types implement trait `WasmClosure`:
(dyn Fn() -> R + 'static)
(dyn Fn(A) -> R + 'static)
(dyn Fn(A, B) -> R + 'static)
(dyn Fn(A, B, C) -> R + 'static)
(dyn Fn(A, B, C, D) -> R + 'static)
(dyn Fn(A, B, C, D, E) -> R + 'static)
(dyn Fn(A, B, C, D, E, F) -> R + 'static)
(dyn Fn(A, B, C, D, E, F, G) -> R + 'static)
and 12 others
note: required by a bound in `Closure::<T>::wrap`
--> /rust/cargo/registry/src/index.crates.io-6f17d22bba15001f/wasm-bindgen-0.2.95/src/closure.rs:254:17
|
254 | T: ?Sized + WasmClosure,
| ^^^^^^^^^^^ required by this bound in `Closure::<T>::wrap`
...
281 | pub fn wrap(mut data: Box<T>) -> Closure<T> {
| ---- required by a bound in this associated function
error[E0599]: the method `forget` exists for struct `Closure<dyn FnOnce(ProgressEvent)>`, but its trait bounds were not satisfied
--> src/view/settings/create.rs:92:17
|
92 | closure.forget();
| ^^^^^^ method cannot be called on `Closure<dyn FnOnce(ProgressEvent)>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`dyn FnOnce(ProgressEvent): WasmClosure`
After giving up on FnOnce. I thought it must be the placement of my parameter copies. So I moved them closer to the function that uses them.
fn PROCESS_IMPORT_OUTSIDE(mut model_copy: Model, mut args_copy: Args, reader_copy: FileReader) {
// snip....
let json = reader_copy
// snip....
args_copy.entries.insert(/* ... */);
controller::stg_data_import(2, &mut model_copy, &mut args_copy);
}
pub fn data_import_stage_1(model: &Model, args: &Args) {
let reader = FileReader::new().expect("Failed to create FileReader");
let reader_event = move |event: ProgressEvent| {
===== ARGS ARE NOW INSIDE READER_EVENT =====
let model_copy = model.clone();
let args_copy = args.clone();
let reader_copy = reader.clone();
// snip....
match event.type_().as_str() {
// snip....
"loadend" => {
// snip....
PROCESS_IMPORT_OUTSIDE(model_copy, args_copy, reader_copy);
}
}
};
let add_event_listeners = || {
let closure = Closure::wrap(Box::new(reader_event) as Box<dyn FnMut(ProgressEvent)>);
let closure_ref = closure.as_ref().unchecked_ref();
// snip....
reader.set_onloadend(Some(closure_ref));
closure.forget();
};
if let Some(files) = files {
if let Some(file) = files.get(0) {
// snip....
add_event_listeners();
reader.read_as_text(&file).expect("Ok: `()`");
}
}
}
But then I ran into this, which tripped me up for about TWO DAYS with me trying everyting under the sun to get the model/args parameters to live as long as the reader parameter.
Things I tried:
- Adding lifetimes.
- Cloning inline rather than using binding variables;
- Moving the clones back outside the block.
- Changing the signature of the parent function to use clones instead of borrows.
- Reading an excellent blog on closures by @RustyYato Closures: Magic Functions
- and so on...
- and so on...
error[E0382]: borrow of moved value: `reader`
--> src/view/settings/create.rs:89:31
|
58 | let reader = FileReader::new().expect("Failed to create FileReader");
| ------ move occurs because `reader` has type `FileReader`, which does not implement the `Copy` trait
...
65 | let reader_event = move |event: ProgressEvent| {
| --------------------------- value moved into closure here
...
69 | let reader_copy = reader.clone();
| ------ variable moved due to use in closure
...
89 | let add_event_listeners = || {
| ^^ value borrowed here after move
...
92 | reader.set_onprogress(Some(closure_ref));
| ------ borrow occurs due to use in closure
error: lifetime may not live long enough
--> src/view/settings/create.rs:90:37
|
57 | ...l: &Model, args: &Args) {
| - let's call the lifetime of this reference `'1`
...
90 | ...wrap(Box::new(reader_event) as Box<dyn FnMut(ProgressEvent)>);
| ^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static`
error: lifetime may not live long enough
--> src/view/settings/create.rs:90:37
|
57 | ...: &Model, args: &Args) {
| - let's call the lifetime of this reference `'2`
...
90 | ...rap(Box::new(reader_event) as Box<dyn FnMut(ProgressEvent)>);
| ^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'2` must outlive `'static`