Simon Crowe
Simon Crowe

Reputation: 468

How can I ensure that a Rust closure implements the `FnMut` trait?

I'm writing a simple Rust program that streams sufficiently loud audio input to an output device.

I got the input and output callbacks to share state by using ringbuf in a manner heavily inspired by this code.

However, when I tried to use a mutable reference of one of my structs in the function, I encountered error E0525:

error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
   --> src/main.rs:59:25
    |
59  | ...et input_data_fn = move |data: &[f32], _: &cpal::InputCallbackInfo| {
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this closure implements `FnOnce`, not `FnMut`
60  | ...   let mut output_fell_behind = false;
61  | ...   massager.ingest(data.to_vec());
    |       -------- closure is `FnOnce` because it moves the variable `massager` out of its environment
...
84  | ...et input_stream = input.build_input_stream(&config, input_data_fn, err_f...
    |                            ------------------          ------------- the requirement to implement `FnMut` derives from here
    |                            |
    |                            required by a bound introduced by this call
    |
note: required by a bound in `build_input_stream`
   --> C:\Users\info\.cargo\registry\src\index.crates.io-6f17d22bba15001f\cpal-0.15.3\src\traits.rs:134:12
    |
125 |     fn build_input_stream<T, D, E>(
    |        ------------------ required by a bound in this associated function
...
134 |         D: FnMut(&[T], &InputCallbackInfo) + Send + 'static,
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `DeviceTrait::build_input_stream`

For more information about this error, try `rustc --explain E0525`.

Here is a simplified minimal example of code that produces this error (and the full source code if anyone is interested):

Note: Apologies for the long example, but build_input_stream is necessary because it requires that the closure implements the FnMut trait.

use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use ringbuf::{
    traits::{Producer, Split},
    HeapRb,
};

fn main() -> anyhow::Result<()> {
    // Setting up audio devices, ignore this code
    let host = cpal::default_host();

    let input = host
        .default_input_device()
        .expect("No input device available");
    println!(
        "Using output device {}",
        input.name().unwrap_or("unnamed".to_owned())
    );
    let input_config = input
        .default_input_config()
        .expect("No default input config");
    let input_sample_format = input_config.sample_format();
    let input_sample_rate = input_config.sample_rate().0 as f32;
    let input_channels = input_config.channels() as usize;

    let output = host
        .default_output_device()
        .expect("No output device available");
    println!(
        "Using output device {}",
        output.name().unwrap_or("unnamed".to_owned())
    );
    let output_config = output
        .default_output_config()
        .expect("No default output config");
    let output_sample_format = output_config.sample_format();
    let output_sample_rate = output_config.sample_rate().0 as f32;
    let output_channels = output_config.channels() as usize;

    let config: cpal::StreamConfig = output_config.into();
    let latency_frames = 1.0 * config.sample_rate.0 as f32;
    let latency_samples = latency_frames as usize * output_channels as usize;

    let ring = HeapRb::<f32>::new(latency_samples * 2);

    // MUTABLE VARIABLES FROM A 3RD PARTY LIBRARY:
    let (mut producer, mut consumer) = ring.split();
    for _ in 0..latency_samples {
        producer.try_push(0.0).unwrap();
    }
    
    // MY MUTABLE VARIABLES:
    let mut massager = DataMassager::new();

    let input_data_fn = move |data: &[f32], _: &cpal::InputCallbackInfo| {
        let mut output_fell_behind = false;
        // THE CALL THAT IS CAUSING PROBLEMS
        massager.ingest(data.to_vec());

        match massager.next() {
            Some(samples) => {
                for sample in samples {
                    if producer.try_push(sample).is_err() {
                        output_fell_behind = true;
                    }
                }
            }
            None => {
                for i in 0..data.len() {
                    if producer.try_push(0.0).is_err() {
                        output_fell_behind = true;
                    }
                }
            }
        };
        if output_fell_behind {
            eprintln!("output stream fell behind: try increasing latency");
        }
    };

    let input_stream = input.build_input_stream(&config, input_data_fn, err_fn, None)?;
    input_stream.play()?;

    Ok(())
}

fn err_fn(err: cpal::StreamError) {
    eprintln!("an error occurred on stream: {}", err);
}

// A useless iterator to illustrate this bug
pub struct DataMassager {
    buffer: Vec<Vec<f32>>,
}

impl DataMassager {
    pub fn new() -> Self {
        let buffer: Vec<Vec<f32>> = vec![];
        return DataMassager { buffer };
    }

    pub fn ingest(mut self, chunk: Vec<f32>) {
        self.buffer.insert(0, chunk);
    }
}

impl Iterator for DataMassager {
    type Item = Vec<f32>;

    fn next(&mut self) -> Option<Self::Item> {
        return self.buffer.pop();
    }
}

Upvotes: 0

Views: 76

Answers (1)

Simon Crowe
Simon Crowe

Reputation: 468

I was wondering why the mutable function try_push didn't result in the same error as my ingest function. It turns out that the answer is in the signature:

    fn try_push(&mut self, elem: Self::Item) -> Result<(), Self::Item> {

It uses &mut -- a mutable reference.

The reason my code would not compile is that I was using mut without any reference, denoting a move rather than a borrow.

    pub fn ingest(mut self, chunk: Chunk) {

The solution is adding one character

    pub fn ingest(&mut self, chunk: Chunk) {

If it wasn't for the constraints on build_input_stream, this code would have resulted in a much more straightforward borrow of moved value error. As far as I can tell this is the underlying error.

Upvotes: 2

Related Questions