rustgtkborrow-checkergtk-rs

How do I create a `glib::MainContext::channel()` outside of the `connect_activate()` and then pass it in?


I am new to rust and GTK. I'd like to create a glib::MainContext::channel() pair prior to initializing my gtk::Application. I believe this can be thought of exactly like a std::sync::mpsc. And while I can create the rx/tx channel pair and save it, I'm not sure how to pass the Receiver into the gtk::Application::connect_activate() signal handler so that I can .attach() it.

For example, I've modified this sample:

struct MyData {
    ready_tx: glib::Sender<i32>,
    //ready_rx: glib::Receiver<i32>,
}

fn setup() -> MyData {
    let (ready_tx, ready_rx) = glib::MainContext::channel(glib::Priority::default());
    MyData {
        ready_tx,
        //ready_rx,
    }
}

fn main() {
    let data = setup();

    //
    // Setup other threads by cloning data.sender
    //

    let application = gtk::Application::new(
        Some("com.github.gtk-rs.examples.cairo_threads"),
        Default::default(),
    );

    application.connect_activate(move |app| {
        build_ui(&app, &data)
    });

    application.run();
}

fn build_ui(application: &gtk::Application, data: &MyData) {
    ...
    data.ready_rx.attach( {
        ...
    });
}

The above compiles and runs (ignoring the stubbed out build_ui) but has an obvious issue: ready_tx isn't actually included.

Doing so results in a "move occurs because data.ready_rx has type glib::Receiver<i32>, which does not implement the Copy trait'" inside build_ui().

I want the ownership of the Receiver<i32> to transit from the setup() function, up to main, and eventually be consumed by the build_ui() function, but I'm not sure how to get there or what I'm missing. I'm not sure why Copy is needed and I know I can't .clone() the Receiver.

Any ideas?

I want to do this because I need to setup some additional threads first that will need references to the Sender well before I initialize the GTK GUI. I haven't found any good examples of creating a glib::MainContext::channel() pair outside of the build_ui() function.

Is there another approach I should consider?

UPDATE:

Thanks to @chayim-friedman's answer, the following modifications to the posted code does what I need:

use std::cell::Cell;

struct MyData {
    ready_tx: glib::Sender<i32>,
    ready_rx: Cell<Option<glib::Receiver<i32>>>,
}

fn setup() -> MyData {
    let (ready_tx, ready_rx) = glib::MainContext::channel(glib::Priority::default());
    MyData {
        ready_tx,
        ready_rx: Cell::new(Some(read_rx)),
    }
}

fn main() {
    let mut data = setup();

    //
    // Setup other threads by cloning data.sender
    //

    let application = gtk::Application::new(
        Some("com.github.gtk-rs.examples.cairo_threads"),
        Default::default(),
    );

    application.connect_activate(move |app| {
        build_ui(&app, &data)
    });

    application.run();
}

fn build_ui(application: &gtk::Application, data: &MyData) {
    ...
    data.ready_rx.take().unwrap().attach( {
        ...
    });
}

Solution

  • If you only need the Receiver once, store an Option<Receiver> and take().unwrap() it in build_ui(), you will need a mutable reference to MyData for that.

    If you cannot provide a mutable reference (which I believe is the case with GTK), you can store a Cell<Option<Receiver>>, and take().unwrap() it. This only requires a shared reference, as Cell has interior mutability.