Tiny gtk-rs problem

Hello. Some months ago I started learning some Rust, and in the last weeks I ported my tiny chess engine from Nim to Rust. Was some work, but at least I got it to compile finally, and I assume that it should work after some debugging. For the plain GTK user interface, it is not that easy unfortunately. As the Nim GUI has only a few lines of code, I had the hope that I would be able to port it in a few days -- but gtk-rs is not that easy. My current problem is to pass a struct containing plain data and additional a GObject as parameter into a closure, which is necessary to use the GObject signals. The GTK-RS book has an example, see Memory Management - GUI development with Rust and GTK 4 Filename: listings/g_object_memory_management/1/main.rs

But these examples pass plain integers, or a GObject, but not a mix. I was able to pass an array or a struct containing plain numbers. But mixed data fail, as in

use std::cell::Cell;
use std::rc::Rc;

use gtk::prelude::*;
use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation};

const APP_ID: &str = "org.gtk_rs.GObjectMemoryManagement2";

#[derive(Debug)]
//#[derive(Clone, Copy, Debug)]
//#[derive(Clone, Debug)]
struct B {
  i: i32,
  f: f64,
  w: Button,
}

fn main() -> glib::ExitCode {
    // Create a new application
    let app = Application::builder().application_id(APP_ID).build();

    // Connect to "activate" signal of `app`
    app.connect_activate(build_ui);

    // Run the application
    app.run()
}
fn build_ui(app: &Application) {
    // Create two buttons
    let button_increase = Button::builder()
        .label("Increase")
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();
    let button_decrease = Button::builder()
        .label("Decrease")
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();

    // Reference-counted object with inner-mutability
    let a = B{i: 0, f: 1.0, w: Button::new()};
    let number = Rc::new(Cell::new(a));

    // Connect callbacks, when a button is clicked `number` will be changed
    let number_copy = number.clone();
    button_increase.connect_clicked(move |_|
      println!("{:?}", a));

    // Add buttons to `gtk_box`
    let gtk_box = gtk::Box::builder()
        .orientation(Orientation::Vertical)
        .build();
    gtk_box.append(&button_increase);
    gtk_box.append(&button_decrease);

    // Create a window
    let window = ApplicationWindow::builder()
        .application(app)
        .title("My GTK App")
        .child(&gtk_box)
        .build();

    // Present the window
    window.present();
}
error[E0382]: use of moved value: `a`
  --> src/main.rs:51:37
   |
46 |     let a = B{i: 0, f: 1.0, w: Button::new()};
   |         - move occurs because `a` has type `B`, which does not implement the `Copy` trait
47 |     let number = Rc::new(Cell::new(a));
   |                                    - value moved here
...
51 |     button_increase.connect_clicked(move |_|
   |                                     ^^^^^^^^ value used here after move
52 |       println!("{:?}", a));
   |                        - use occurs due to use in closure

For more information about this error, try `rustc --explain E0382`.
warning: `cb` (bin "cb") generated 1 warning
error: could not compile `cb` (bin "cb") due to previous error; 1 warning emitted

In the Nim code, I was lazy and passed a GObject and additional used global data. Global data is ugly, I better had combined all into one passed struct. So I try that now in Rust, converting this Nim function: salewski-chess/board_gtk4.nim at main · StefanSalewski/salewski-chess · GitHub

Putting #[derive(Clone, Copy, Debug)] in front of the struct does not work, when the struct contains the Button field, which is a GObject. I might guess that I have to implement a copy or clone trait manually. But I have no idea if the guess is right, if it is possible, and how to do it. I would like to spent not too much time with gtk-rs now, as I am not sure if I will really continue using Rust. And when I do, I may later prefer a native Rust GUI like Xilem/Druid or a similar one.

Another tiny issue is, that I was not able to find the gtk-rs method Gtk.Widget.get_root which is used as window = Window(widget.getRootWidget) in the Nim code of pressed() function.

this line moved a into number, but you later want to print the value of a, which doesn't exist any more:

this is what the compile error means.

also, Cell is mostly useful for Copy types, but your struct B cannot be Copy (or even Clone), because the Button widget cannot be cloned. I think you meant to use RefCell. Rc<RefCell<T>> is the common pattern to emulate reference counted shared states (only allow single thread access).

bythe variable name button_increase and button_decrease, I would guess probably this is what you want:

    let a = B{i: 0, f: 1.0, w: Button::new()};
    let number = Rc::new(RecCell::new(a));

    let number_copy = number.clone();
    button_increase.connect_clicked(move |_| {
        number_copy.borrow_mut().i += 1;
    });
    button_decrease.connect_clicked(move |_| {
        number.borrow_mut().i -= 1;
    });

I don't know why you put a Button in your share states though, generally it's not good practice to mix UI widgets with app states.

you cannot clone a Button, so if your struct contains a Button as field, you cannot derive Clone for your struct. in theory you can implement Clone manually, but I don't see how you can do it in a sensible way, as you cannot clone the widget anyway, if you create a new Button widget each time your struct is cloned, does that count as a Clone???

if you already have a working solution in other languages, you don't need to migrate the whole system into rust. if you feel like to use rust, you can implement the performance critical part of your system in rust and keep other components (e.g. the UI part) in their current status, as long the language you are using supports C ABI.

your link to the gtk document is for gtk4, so you should use the gtk4 crate instead of gtk, which is binding for gtk3, and gtk3 doesn't have a Widget.get_root() method. there's similar named Widget.get_root_window(), but it's a gtk2 legacy API and is marked deprecated for gtk3.

to call the methods on Widget class in gtk4, you should use the WidgetExt extension trait, specifically, the equivalence of Widget.get_root() is WidgetExt::root() method:

Thank you very much for your detailed explanations. Actually, I already tried to delete this thread yesterday, because my initially post had bceome some sort of outdated by my own little progress. With use of RefCell I got a first tiny working example:

use std::cell::RefCell;
use std::rc::Rc;

use gtk::prelude::*;
use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation};

const APP_ID: &str = "org.gtk_rs.GObjectMemoryManagement2";

#[derive(Debug)]
//#[derive(Clone, Copy, Debug)]
//#[derive(Clone, Debug)]
struct B {
  i: i32,
  f: f64,
  w: Button,
}

fn main() -> glib::ExitCode {
    // Create a new application
    let app = Application::builder().application_id(APP_ID).build();

    // Connect to "activate" signal of `app`
    app.connect_activate(build_ui);

    // Run the application
    app.run()
}
fn build_ui(app: &Application) {
    // Create two buttons
    let button_increase = Button::builder()
        .label("Increase")
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();
    let button_decrease = Button::builder()
        .label("Decrease")
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();

    // Reference-counted object with inner-mutability
    let a = B{i: 0, f: 1.0, w: Button::new()};
    let number = Rc::new(RefCell::new(a));

    // Connect callbacks, when a button is clicked `number` will be changed
    let number_copy = number.clone();
    button_increase.connect_clicked(move |_|
      {
      number.borrow_mut().i += 1;
      println!("{:?}", number.borrow().i)});

    // Add buttons to `gtk_box`
    let gtk_box = gtk::Box::builder()
        .orientation(Orientation::Vertical)
        .build();
    gtk_box.append(&button_increase);
    gtk_box.append(&button_decrease);

    // Create a window
    let window = ApplicationWindow::builder()
        .application(app)
        .title("My GTK App")
        .child(&gtk_box)
        .build();

    // Present the window
    window.present();
}

And late yesterday evening, I even get gesture support working for my drawing area.

I will study all of your comments carefully. Now I have again some confidence that porting my tiny Nim GTK4 GUI to Rust should be possible within a few days. I am still not sure which is the best location to asking gtk-rs questions: I am looking from time to time into the Gnome forum, but there is typically not a lot gtk-rs traffic. The gtk-rs Github issue tracker or its tiny discussion forum seems to be an alternative, but I do not really want to bother the few gtk-rs devs with my beginner questions. StackOverflow or the Rust Zulip forum would be possible resources as well, but as this Rust forum has many bright and friendly Rust users, I think I will continue here.

Best regards,

Dr. Stefan Salewski

if you are already familiar with gtk, you just need to learn how gtk-rs maps the gtk concepts onto rust constructs. the progress really only depends on how much you know rust the language.

I suggest you read the gtk-rs book first, especially chapter 4 and chapter 5, they are not very long, read them slowly, then you'll have a pretty good picture of how to map gtk features onto the rust library and where to look for documents.

after that, it's just a matter of learning unfamiliar concepts of rust language as you encounter them. feel free to ask any rust related questions. people of the rust community are awesome.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.