如何从多次运行的移动 FnMut 闭包中读取文件?

San*_*nes 2 closures file move-semantics rust

我正在使用glutin,所以我的程序的主循环有一个移动关闭,我正在尝试使用rodio板条箱播放音频文件。使用以下代码,一切正常,每次程序循环时我都会发出一声哔哔声:

...
let sink: rodio::Sink = ...;
event_loop.run(move |event, _, control_flow | {
    let sound_file = File::open("beep.ogg").unwrap();
    let buf_wrap = BufReader::new(sound_file);
    let source = rodio::Decoder::new(buf_wrap).unwrap();
    sink.append(source);
    ...
});
...
Run Code Online (Sandbox Code Playgroud)

但是,这非常慢,因为我每次循环时都打开同一个文件,所以我尝试使用以下方法修复它:

...
let sound_file = File::open("beep.ogg").unwrap();
event_loop.run(move |event, _, control_flow | {
    let buf_wrap = BufReader::new(sound_file);
    ...
});
...
Run Code Online (Sandbox Code Playgroud)

但是现在编译器给了我以下错误信息:

error[E0507]: cannot move out of `sound_file`, a captured variable in an `FnMut` closure
  --> src/lib.rs:86:33
   |
83 |     let sound_file = File::open("beep.ogg").unwrap();
   |         ---------- captured outer variable
...
86 |         let buf_wrap = BufReader::new(sound_file);
   |                                       ^^^^^^^^^^ move occurs because `sound_file` has type `File`, which does not implement the `Copy` trait
Run Code Online (Sandbox Code Playgroud)

我一直试图解决这个问题一段时间没有成功,任何见解将不胜感激。

Cry*_*jar 5

问题

基本上,手头的问题是rodio::Decoder::new消耗它从中读取的值(好吧,实际上它已经被 消耗了BufReader::new)。所以,如果你有一个可以被多次调用的循环或闭包,你必须每次都想出一个新的值。这File::open在你的第一个代码中做了什么。

在你的第二个代码剪断,你只能创建File 一次,然后再尝试使用它时间,这锈病的所有权概念阻止你做的事情。

另请注意,遗憾的是,使用引用并不是真正的选项,rodio因为解码器必须是'static(例如,参见Sink::appendtrait bound on S)。

解决方案

如果您认为您的文件系统有点慢,并且您想对其进行优化,那么您实际上可能想要预先读取整个文件(这File::open并不可行)。这样做也应该为你提供一个缓冲(例如Vec<u8>),您可以克隆,从而允许重复创建新的,可以通过消耗值Decoder。这是一个执行此操作的示例:

use std::io::Read;
let sink: rodio::Sink = /*...*/;
// Read file up-front
let mut data = Vec::new();
let mut sound_file = File::open("beep.ogg").unwrap();
sound_file.read_to_end(&mut data).unwrap();

event_loop.run(move |event, _, control_flow | {
    // Copies the now in-memory file content
    let cloned_data = data.clone();
    // Give the data a `std::io::Read` impl via the `std::io::Cursor` wrapper
    let buffered_file = Cursor::new(cloned_data);
    let source = rodio::Decoder::new(buffered_file).unwrap();
    sink.append(source);
});
Run Code Online (Sandbox Code Playgroud)

然而,以我个人的经验,rodio还有相当多的加工后正在做Decoder,所以我也做了解码的前期和使用rodio::source::Buffered的包装,像这样的:

use std::io::Read;
let sink: rodio::Sink = /*...*/;
// Read & decode file
let sound_file = File::open("beep.ogg").unwrap();
let source = rodio::Decoder::new(file).unwrap();
// store the decoded audio in a buffer
let source_buffered = source.buffered();

// At least in my version, this buffer is lazyly initialized,
// So, maybe, you also want to initialize it here buffer, e.g. via:
//source_buffered.clone().for_each(|_| {})

event_loop.run(move |event, _, control_flow | {
    // Just copy the in-memory decoded buffer and play it
    sink.append(source_buffered.clone());
});
Run Code Online (Sandbox Code Playgroud)

如果您在多线程环境中使用它,或者就像静态一样,您也可以使用lazy_staticcrate 使这些rodio::source::Buffered实例在整个程序中可用,而只进行一次初始化。