在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库需要在启动时传递给application.connect_activate的Fn回调,所以我不能使用FnOnce或FnMut闭包来移动回调中的glib::Receiver。编译器抛出以下错误:

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

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

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闭包内或在回调中解引用它而不会导致错误?

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

当你需要一些东西是可变的,即使你在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()Cell中移除Option<Receiver>,留下Noneexpect然后删除Option包装器(并处理在这种情况下通过panic调用函数两次的可能性)。

应用到你原来的问题,这将是:

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

最新更新