【问题标题】:Safely move or dereference Receiver in a Fn?在 Fn 中安全地移动或取消引用 Receiver?
【发布时间】:2021-12-04 11:08:45
【问题描述】:

我正在开发一个应用程序,该应用程序可选使用 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,或者在回调中取消引用它而不会导致错误?

【问题讨论】:

    标签: rust rust-gnome


    【解决方案1】:

    当您需要从代码中移出一个值时,根据类型系统但实际上不会被多次调用,可以使用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&lt;Receiver&gt;,将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

    【讨论】:

    • 这里的问题是,如果我理解正确的话,这会导致 FnMut 实现,它不符合 Fn 的要求。如果我使用您的示例(并且还使 window_rx)选项可变,我会收到此错误:错误 [E0596]:不能借用 window_rx 作为可变,因为它是 Fn 闭包中的捕获变量
    • @rumdrums 啊,哎呀,没错。您将需要一些涉及内部可变性的解决方案,CellRefCell(可能包含 Option)。
    • @rumdrums 为答案添加了工作代码。
    • 这太棒了——谢谢!
    猜你喜欢
    • 1970-01-01
    • 2015-12-20
    • 1970-01-01
    • 2012-03-19
    • 2022-11-18
    • 2015-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多