1

I'm working on an app that optionally uses a GUI to display video data that's roughly structured like this:

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( ... );
}

The gtk rust library requires a Fn callback passed to application.connect_activate on startup, so I can't use a FnOnce or FnMut closure to move the glib::Receiver in the callback. The compiler throws this error:

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

I've tried to avoid the move by wrapping window_rx in a Rc, ie:

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

But upon dereferencing the Rc in my build_ui function, I get this error:

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

The fallback I've used thus far is to just move the channel creation and thread creation into my build_ui function, but because the GUI is not required, I was hoping to avoid using GTK and the callback entirely if GUI is not used. Is there some way I can either safely move window_rx within a closure or otherwise dereference it in the callback without causing an error?

rumdrums
  • 1,322
  • 2
  • 11
  • 25

1 Answers1

2

When you need to move a value out from code that, by the type system but not in practice, could be called more than once, the simple tool to reach for is Option. Wrapping the value in an Option allows it to be swapped with an Option::None.

When you need something to be mutable even though you're inside a Fn, you need interior mutability; in this case, Cell will do. Here's a complete compilable program that approximates your situation:

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() removes the Option<Receiver> from the Cell, leaving None in its place. The expect then removes the Option wrapper (and handles the possibility of the function being called twice by panicking in that case).

Applied to your original problem, this would be:

    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")); 
    });

However, be careful: if the library requires a Fn closure, there might be some condition under which the function could be called more than once, in which case you should be prepared to do something appropriate in that circumstance. If there isn't such a condition, then the library's API should be improved to take a FnOnce instead.

Kevin Reid
  • 37,492
  • 13
  • 80
  • 108
  • 1
    The problem here, if I understand correctly, is that this results in a `FnMut` implementation, which doesn't meet requirements of `Fn`. If I use your example (and also make window_rx) Option mutable, I get this error: error[E0596]: cannot borrow `window_rx` as mutable, as it is a captured variable in a `Fn` closure – rumdrums Oct 16 '21 at 19:36
  • 1
    @rumdrums Ah, oops, that's right. You will need some solution involving interior mutability, a `Cell` or `RefCell` (probably _containing_ the `Option`). – Kevin Reid Oct 16 '21 at 22:32
  • @rumdrums Added working code to the answer. – Kevin Reid Oct 16 '21 at 22:55
  • This is wonderful -- thanks! – rumdrums Oct 17 '21 at 04:17