How to refactor closure that captures and moves into function or method


#1

Dear Rustaceans,

as a Rust beginner, I am looking for a way to refactor a long closure into a function or method. Coming from Go, I am unsure on how to proceed in Rust and kindly ask you for hints and ideas. Here’s the story:

I am using gstreamer-rs to set up a sound recording pipeline. To get samples, I can create a gstreamer_app::AppSink and set callbacks for it:

fn create_pipeline() -> Result<gst::Pipeline, Error> {
    // [...]
    let sink = gst::ElementFactory::make("appsink", None).ok_or(MissingElement("appsink"))?;

    let appsink = sink
        .dynamic_cast::<gst_app::AppSink>()
        .expect("Sink element is expected to be an appsink!");

    appsink.set_callbacks(
        gst_app::AppSinkCallbacks::new()
            .new_sample(|appsink|) {
                // ... LONG CLOSURE. Here I collect samples and do stuff with them.
            }).build(),
    );
    // [...]
}

Besides other things I want to collect samples of multiple closure calls into a larger buffer. Since gstreamer uses multiple threads , I went with big_buffer: std::sync::Mutex<VecDeque<i16>> (many thanks to @slomo for pointing me in the right direction in the gstreamer IRC channel). So now the closure captures and moves big_buffer.

fn create_pipeline() -> Result<gst::Pipeline, Error> {
    // [...]
    let big_buffer = Mutex::new(VecDeque::<i16>::new());

    appsink.set_callbacks(
        gst_app::AppSinkCallbacks::new()
            .new_sample(move |appsink|) {
                // ... In the long closure, the  samples eventually end up in new_samples: [i16]
                let mut big_buffer = big_buffer.lock().unwrap();
                big_buffer.extend(new_samples.iter());
                // more stuff happening with the samples
            }
    // [...]
}

It it weren’t for the capturing and move of big_buffer, refactoring the callback into a dedicated fn would be easy for me. In Go I would put big_buffer in a struct and then use a method of that struct as parameter to new_sample.

But what would be the idiomatic way for Rust?

I have the following code working, but I lack the Rust experience to judge it and would appreciate a review.

/// A helper struct for providing the variables otherwise captured by a closure.
struct BB {
    big_buffer: VecDeque<i16>,
}

impl BB {
    fn new() -> BB {
        let b = BB {
            big_buffer: VecDeque::<i16>::new(),
        };
        b
    }

    fn long_closure(&mut self, appsink: &gst_app::AppSink) -> gst::FlowReturn {
        // ALL THE LONG STUFF
    }
}

// [...]


fn create_pipeline() -> Result<gst::Pipeline, Error> {
    // [...]

    let b = Mutex::new(BB::new()); // instead of the Mutex<VecDeque<i16>> from before

    appsink.set_callbacks(
        gst_app::AppSinkCallbacks::new()
            .new_sample(move |appsink| {
                let mut b = b.lock().unwrap();
                b.long_closure(appsink)
            }).build(),
    );

    // [...]
}

#2

A closure that captures something is essentially a struct, so your solution with struct BB seems to be fine. It’s also possible to avoid a new struct like so:

fn long_fn(big_buffer: &mut VecDeque<i16>, appsink: &gst_app::AppSink)
    -> gst::FlowReturn 
{
    //...
}
//...
            .new_sample(move |appsink| {
                let mut b = b.lock().unwrap();
                long_fn(&mut big_buffer, appsink)
            }).build(),

But this doesn’t scale well when the number of variables you need to capture increases. The solution with a struct would work better in that case.