Copying closure within closure

I'm still working through this issue with memory leaking (OOM) issues and during my refactoring came across this situation:

One of my long-lived closures contains another long-lived closure.

The issue for me is, right now I can create a closure within a method, push it to a vec and I can drop it in another method (which represents a different view). -- things were looking up for a while.

But what about the closure that exists within a closure? It's a wasm_bindgen closure over a box over a move |progressEvent| {}

  • I can't update the closure-array on Self directly because I can't move Self into the closure;
  • Updating a temporary variable will trigger a move and the lack of a copy trait.
  • Same deal if I pass in a variable via the caller / parent method.
  • Same deal if the closure-array resides in a different Struct.
  • Using std::mem::take had no effect that I could see (array not updated).
  • And so on....

I was hoping to do this without using refcell but I'm not good at that yet. Is there no other way?

My next idea is to break up the closures entirely (return early from the parent closure with the reader, and then pass it to a separate method on self where the child closure would no longer be nested.


error[E0382]: use of moved value: `p`
   --> src/modules/templates/settings.rs:329:18
    |
47  |         let mut p = vec![];
    |             ----- move occurs because `p` has type `Vec<{closure@src/modules/templates/settings.rs:265:17: 265:44}>`, which does not implement the `Copy` trait
...
259 |         let data_import_cl = Closure::<dyn FnMut(Event)>::new(move |event: Event| {
    |                                                               ------------------- value moved into closure here
...
285 |             p.push(progress_event.clone());
    |             - variable moved due to use in closure
...
329 |         self.add(p);
    |                  ^ value used here after move

Stripped down method:

pub struct Template {
    // ...snip...
    pub plisteners: Vec<Closure<dyn FnMut(ProgressEvent)>>,
    // ...snip...
}

impl Template {

    pub fn add(&mut self, events: Vec<impl FnMut(ProgressEvent) + 'static>) {
        // TEST: INDIRECT PUSH TO PLISTENERS
        for event in events {
        self.plisteners.push(Closure::wrap(Box::new(event) as Box<dyn FnMut(ProgressEvent)>));
        }
    }
    // ...snip...

    pub fn view_settings(&mut self) {
        // ...snip...

        let mut p = vec![];
        // TEST: INDIRECT PUSH TO PLISTENERS

        let data_import_cl = Closure::<dyn FnMut(Event)>::new(move |event: Event| {
            event.prevent_default();
            let reader = FileReader::new().expect("Some: `FileReader`");

            let progress_event = {
                let reader_copy = reader.clone();
                move |event: ProgressEvent| {
                    // ...snip...
                    Template::data_import_process(&reader_copy); // no `self`
                    // ...snip...
                }
            };

            let add_event_listeners = || {
                let reader_et: EventTarget = reader.clone().into();
                let reader_cl = Closure::wrap(Box::new(progress_event.clone()) as Box<dyn FnMut(ProgressEvent)>);

                // ^^^^^^^^^^^^^^^
                reader_et
                    .add_event_listener_with_callback("progress", reader_cl.as_ref().unchecked_ref())
                    .expect("Failed: `unchecked_ref()`");
                reader_et
                    .add_event_listener_with_callback("loadend", reader_cl.as_ref().unchecked_ref())
                    .expect("Failed: `unchecked_ref()`");
                // ^^^^^^^^^^^^^^^
                // THE CLOSURES I WANT TO SAVE REFERENCES TO AND DROP

                reader_cl.forget();
            };

            p.push(progress_event.clone());
            // p.push((reader.clone().into(), String::from("progress"), Closure::wrap(Box::new(progress_event.clone()) as Box<dyn FnMut(ProgressEvent)>)));
            // p.push((reader.clone().into(), String::from("onloadend"), Closure::wrap(Box::new(progress_event.clone()) as Box<dyn FnMut(ProgressEvent)>)));

            // ...snip...

            if let Some(files) = files {
                if let Some(file) = files.get(0) {
                    // ...snip...
                    add_event_listeners();
                    // ...snip...
                }
            }
        });

        let data_import_et: EventTarget = data_import_el.clone().into();
        data_import_et
            .add_event_listener_with_callback("change", data_import_cl.as_ref().unchecked_ref())
            .expect("Failed: `unchecked_ref()`");
        self.listeners
            .push((data_import_et, String::from("change"), data_import_cl));
        // ^^^^^^^^^^^^^^^
        // THIS CLOSURE I CAN CAPTURE AND LATER DROP

        self.add(p);
        // ^^^^^^^^^^^^^^^
        // THIS DOES NOT WORK
    }

    // helper function
    fn data_import_process(_reader: &FileReader) {
        // ...snip...
    }
}

Is it practical to not register the nested closure in the Vec? When the parent closure is dropped, the nested closure will also be dropped.

I couldn't tell whether you'd already considered this.

I did think of it and then said, "Nah. That's too convenient. It would be like garbage collection." But I didn't look up anything specific on that subject.

If you're worried it would be slow because it is dropped automatically when the parent closure is dropped -- it won't. All Rust objects are dropped this way.

My old rust code crashing my computer and forcing reboots, was not a good thing for my storage media. I now have one of bash scripts playing OOM killer to keep my data safe. So I'm a little zealous right now about getting a handle/copy on any of my rust code that uses .forget().

Knowing that child closures are automatically dropped will save me from some hand-wringing while porting the remainder of my codebase. If you know where in the rust docs it talks about it can you add the link?

I see this for refcell (which I need to study anyways):

It's fundamental to RAII, which is how Rust objects are dropped and deallocated. The details for Rust are in the reference here:
https://doc.rust-lang.org/1.82.0/reference/destructors.html#destructors

Thank you for this link!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.