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();
}
}
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.