gtk-rs: Does a gtk4 widget hold a reference to its callbacks while a gtk3 widget doesn't?

168 Views Asked by At

I am porting a working gtk-rs application from gtk3 to gtk4. The following code is a reduced self-contained example to demonstrate the Problem. The code is for gtk3 with the changes required for gtk4 indicated by comments.

use gtk::prelude::*;
use gtk::{ApplicationWindow, DrawingArea};
use std::cell::RefCell;
use std::rc::Rc;

struct Ctx {
    text: String,
    _drawing_area: gtk::DrawingArea,
}

impl Drop for Ctx {
    fn drop(&mut self) {
        println!("DROP");
    }
}

fn on_activate(app: &gtk::Application) {
    let drawing_area = DrawingArea::builder().build();

    let win = ApplicationWindow::builder()
        .application(app)
        .child(&drawing_area)
        .build();

    // gtk3:
    win.show_all();

    // gkt4:
    //win.show();

    let ctx = Ctx {
        text: "Hallo".into(),
        _drawing_area: drawing_area.clone(),
    };
    let shared_context = Rc::new(RefCell::new(ctx));

    {
        let shared_context = shared_context.clone();

        // gtk3:
        drawing_area.connect_draw(move |_area, _cairo| {
            let ctx = shared_context.borrow();
            println!("draw text= \"{}\"", ctx.text);
            gtk::Inhibit(false)
        });

        // gtk4:
        //drawing_area.set_draw_func(move |_area, _cairo, _width, _height| {
        //    let ctx = shared_context.borrow();
        //    println!("draw text= \"{}\"", ctx.text);
        //});        
    }
}

fn main() {
    let app = gtk::Application::builder().build();
    app.connect_activate(on_activate);
    app.run();
    println!("main loop finished");
}

With gtk3, the code works as intended: The Ctx structure is dropped when the window is closed:

     Running `target/debug/droptest`
draw text= "Hallo"
DROP
main loop finished

With gtk4, however, the Ctx structure is not dropped:

     Running `target/debug/droptest`
draw text= "Hallo"
main loop finished

I see, that there is a circular reference ctx -> drawing_area -> draw_func -> closure -> shared_context -> ctx. I assume that is the reason, that ctx is not dropped with gtk4.

However, I'd expected gtk3 and gtk4 (-rs) to behave identically. Where does the difference come from? Is it documented?

Any suggestions to solve the problem with gtk4? The closure could get a weak reference, but then ctx would be dropped when on_active() is finished. Is there an elegant way to reference ctx from the ApplicationWindows once? In reality, of course, Ctx contains many widgets and other data and there are multiple callbacks.

1

There are 1 best solutions below

0
On

As nobody jumped on this, I try to answer that myself:

The observed different behavior can be demonstrated with the C api of gtk as well, it is not rooted in gtk-rs. Circular reference are not reliably resolved by the gtk runtime, although sometimes it works with gtk3 when a window is closed.

See Why does gtk3 call the ClosureNotify function in the example code, but gkt4 doesn't?

The possible approaches to resolve the problem of the reference cycle mentioned in the link above work for gtk-rs, too:

  • Subclass one of the widgets and make the ctx data part of it
  • For gtk4: Use a "win.close" action
  • Use weak references in ctx