如何从带有“静态绑定”的闭包中构建容器?

How can I construct a container from within a closure with a 'static bound?

我正在使用 libpulse_binding library, and I'm trying to obtain a sequence of SinkInputInfos from the get_sink_input_info_list 函数:

pub fn get_sink_input_info_list<F>(
    &self,
    callback: F,
) -> Operation<dyn FnMut(ListResult<&SinkInputInfo>)>
where
    F: FnMut(ListResult<&SinkInputInfo>) + 'static,

该函数接受一个回调,并为它产生的每个 SinkInputInfo 调用一次。我正在尝试将所有这些 SinkInputInfo 收集到一个列表中,以便我可以更清楚地了解世界状况。令人恼火的是,SinkInputInfo 没有实现 CopyClone,所以我创建了一个自定义结构并实现了 From 以从 SinkInputInfo 中获取有用的信息:

struct StreamInfo {
    readable_name: String,
    binary_name: String,
    pid: String,
}

impl From<&pulse::context::introspect::SinkInputInfo<'_>> for StreamInfo {
    fn from(info: &pulse::context::introspect::SinkInputInfo) -> Self {
        let name = info.proplist.gets("application.name").unwrap();
        let binary = info.proplist.gets("application.process.binary").unwrap();
        let pid = info.proplist.gets("application.process.id").unwrap();
        StreamInfo {
            readable_name: name,
            binary_name: binary,
            pid: pid,
        }
    }
}

但是,这似乎不起作用。我有以下代码:

let mut sink_infos: Vec<StreamInfo> = Vec::new();
let op = introspector.get_sink_input_info_list(|result| match result {
    pulse::callbacks::ListResult::Item(info) => sink_infos.push(info.into()),
    pulse::callbacks::ListResult::End => {},
    pulse::callbacks::ListResult::Error => panic!("Error getting sink input info"),
});

但无法编译:

error[E0373]: closure may outlive the current function, but it borrows `sink_infos`, which is owned by the current function
  --> src/bin/play-pause.rs:49:52
   |
49 |     let op = introspector.get_sink_input_info_list(|result| match result {
   |                                                    ^^^^^^^^ may outlive borrowed value `sink_infos`
50 |         pulse::callbacks::ListResult::Item(info) => sink_infos.push(info.into()),
   |                                                     ---------- `sink_infos` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src/bin/play-pause.rs:49:14
   |
49 |       let op = introspector.get_sink_input_info_list(|result| match result {
   |  ______________^
50 | |         pulse::callbacks::ListResult::Item(info) => sink_infos.push(info.into()),
51 | |         pulse::callbacks::ListResult::End => {},
52 | |         pulse::callbacks::ListResult::Error => panic!("Error getting sink input info"),
53 | |     });
   | |______^
help: to force the closure to take ownership of `sink_infos` (and any other referenced variables), use the `move` keyword
   |
49 |     let op = introspector.get_sink_input_info_list(move |result| match result {
   |                                                    ^^^^^^^^^^^^^

tldr:闭包必须具有 'static 生命周期,因为 libpulse_binding 是这么说的(大概是因为它被交给了 PulseAudio C API,它可以随心所欲地做任何事情与它),但 sink_infos 不是 'static,闭包必须借用 sink_infos 才能附加到它,这使得闭包不是 'static,IIUC.

我怎样才能制作一个Vec(或任何容器,我不挑剔)给定一个 'static 闭包,这个闭包被 [=31 重复调用]SinkInputInfos =]?

这里的基本问题是您 运行 进入 Rust's borrowing rules:

Given an object T, it is only possible to have one of the following:

  • Having several immutable references (&T) to the object (also known as aliasing).
  • Having one mutable reference (&mut T) to the object (also known as mutability).

您正在尝试保留对 Vec 的引用 &Vec(以便您以后可以使用它),同时尝试在闭包中向其添加内容(即 &mut Vec)。 Rust 不知道你不会在闭包使用 &mut Vec 时尝试使用 &Vec,所以它不会让你在闭包中创建一个 &mut Vec仍然是 &Vec 在闭包外徘徊。

您可以做的下一个最好的事情是使用 Rc。这将允许您避开编译器的借用检查,而是将其推迟到运行时。然而:这意味着如果你在你的程序 运行 时试图违反借用规则,它会 panic 而不是编译时错误!

在大多数情况下,您可以将 Rc<Vec<_>> 视为普通的 Vec,因为 Rc 实现了 Deref.

由于您还希望能够改变 Vec 以便向其添加内容,因此您还需要将其放入 RefCell 中。这将锁定 Vec,确保您一次只有一个 &mut Vec 可用,如果您有 &Vec,则不能有 [=15] =](同样,如果你试图违反规则,你的程序将会崩溃)。您可以在 RefCell 上使用 .borrow().borrow_mut() 方法来获取对 Vec 的共享和可变引用(如果您可以做一些事情,这些方法还有 try_* 变体如果不可能借用是明智的。

如果您不使用 RefCell,您将只能从 Rc 获得 immutable/shared 引用(&Vec)(除非您只有一个 Rc,但那样你就不需要 Rc!)

尝试如下操作:

use std::cell::RefCell;
use std::rc::Rc;

let sink_infos: Rc<RefCell<Vec<StreamInfo>>> = Rc::new(RefCell::new(Vec::new()));
let sink_infos2 = sink_infos.clone(); // Create a new Rc which points to the same data.
let op = introspector.get_sink_input_info_list(move |result| match result {
    pulse::callbacks::ListResult::Item(info) => sink_infos2.borrow_mut().push(info.into()),
    pulse::callbacks::ListResult::End => {},
    pulse::callbacks::ListResult::Error => panic!("Error getting sink input info"),
});