0

我正在开发一个应用程序,该应用程序可以选择使用 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 库需要application.connect_activate在启动时传递一个 Fn 回调,所以我不能使用 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,如果不使用 GUI,我希望完全避免使用 GTK 和回调。有什么方法可以安全地window_rx在闭包内移动,或者在回调中取消引用它而不会导致错误?

4

1 回答 1

1

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

当你需要某些东西是可变的,即使你在 a 内部Fn,你也需要内部可变性;在这种情况下,Cell会做。这是一个与您的情况近似的完整可编译程序:

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()Option<Receiver>从中删除Cell,留None在原处。然后expect删除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取而代之。

于 2021-10-16T19:29:39.070 回答