如何确保 Rust 闭包实现 `FnMut` 特性?

问题描述 投票:0回答:1

我正在编写一个简单的 Rust 程序,它将足够响亮的音频输入传输到输出设备。

我通过使用ringbuf获得输入和输出回调来共享状态,其方式深受此代码的启发。

但是,当我尝试在函数中使用我的结构之一的可变引用时,我遇到了错误 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`.

这是产生此错误的代码的简化最小示例(以及完整源代码,如果有人感兴趣):

注意:对长示例表示歉意,但是

build_input_stream
是必要的,因为它要求闭包实现
FnMut
特性。

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();
    }
}
rust ownership
1个回答
2
投票

我想知道为什么可变函数 try_push 没有导致与我的

ingest
函数相同的错误。原来答案就在签名里:

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

它使用

&mut
——可变引用。

我的代码无法编译的原因是我在没有任何引用的情况下使用了

mut
,表示移动而不是借用。

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

解决方案是添加一个字符

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

如果没有 build_input_stream 上的

constraints
,这段代码会导致更直接的
borrow of moved value
错误。据我所知,这是根本错误。

© www.soinside.com 2019 - 2024. All rights reserved.