在 Fn 中安全地移动或取消引用 Receiver?

Safely move or dereference Receiver in a Fn?

我正在开发一个 可选 使用 GUI 来显示大致结构如下的视频数据的应用程序:

fn main() {
    let (window_tx, window_rx)  =
        MainContext::channel::<MyStruct>(PRIORITY_DEFAULT);

    let some_thread = thread::spawn(move || -> () {
        // send data to window_tx
    });

    let application =
        gtk::Application::new(Some("com.my.app"), Default::default());

    application.connect_activate(move |app: &gtk::Application| {
        build_ui(app, window_rx); 
    });

    application.run();

    some_thread.join().unwrap();
}

fn build_ui(application: &gtk::Application, window_rx: Receiver<MyStruct>) {
  window_rx.attach( ... );
}

gtk rust 库需要在启动时将 Fn 回调传递给 application.connect_activate,因此我无法使用 FnOnce 或 FnMut 闭包在回调中移动 glib::Receiver。编译器抛出此错误:

error[E0507]: cannot move out of `window_rx`, a captured variable in an `Fn` closure

我试图通过将 window_rx 包装在 Rc 中来避免移动,即:

    let r = Rc::new(RefCell::new(window_rx));
    application.connect_activate(move |app: &gtk::Application| {
        build_ui(app, Rc::clone(&r)); 
    });

但是在我的 build_ui 函数中取消引用 Rc 时,我得到了这个错误:

error[E0507]: cannot move out of an `Rc`

到目前为止我使用的回退方法是将通道创建和线程创建移到我的 build_ui 函数中,但是因为不需要 GUI,我希望避免使用 GTK 和回调如果不使用 GUI,则完全如此。有什么方法可以安全地在闭包中移动 window_rx 或者在回调中取消引用它而不会导致错误?

当您需要从代码中移出一个值时,通过类型系统而不是在实践中,可以多次调用,可以使用的简单工具是 Option。将值包装在 Option 中允许它 交换 Option::None.

当您需要某些东西即使在 Fn 中也是可变的时,您需要内部可变性;在这种情况下,Cell 就可以了。这是 a complete compilable program 最接近您的情况:

use std::cell::Cell;

// Placeholders to let it compile
use std::sync::mpsc;
fn wants_fn_callback<F>(_f: F) where F: Fn() + 'static {}
struct MyStruct;

fn main() {
    let (_, window_rx) = mpsc::channel::<MyStruct>();
    
    let window_rx: Cell<Option<mpsc::Receiver<MyStruct>>> = Cell::new(Some(window_rx));
    wants_fn_callback(move || {
        let _: mpsc::Receiver<MyStruct> = window_rx.take().expect("oops, called twice"); 
    });
}

Cell::take()Cell 中删除 Option<Receiver>,留下 Noneexpect 然后删除 Option 包装器(并在这种情况下通过恐慌处理函数被调用两次的可能性)。

应用于您的原始问题,这将是:

    let window_rx: Option<Receiver<MyStruct>> = Cell::new(Some(window_rx));
    application.connect_activate(move |app: &gtk::Application| {
        build_ui(app, window_rx.take().expect("oops, called twice")); 
    });

但是,请注意:如果库需要 Fn 闭包,则在某些情况下可能会多次调用该函数,在这种情况下,您应该准备好在那种情况。如果没有这种情况,那么库的 API 应该改进为 FnOnce 代替。