diff --git a/book/listings/Cargo.toml b/book/listings/Cargo.toml index a34202f60627..593e0ed086bb 100644 --- a/book/listings/Cargo.toml +++ b/book/listings/Cargo.toml @@ -102,6 +102,10 @@ path = "g_object_memory_management/3/main.rs" name = "g_object_memory_management_4" path = "g_object_memory_management/4/main.rs" +[[bin]] +name = "g_object_memory_management_5" +path = "g_object_memory_management/5/main.rs" + # g_object_properties [[bin]] name = "g_object_properties_1" diff --git a/book/listings/g_object_memory_management/1/main.rs b/book/listings/g_object_memory_management/1/main.rs index 44262976ee9d..f4d987ef412b 100644 --- a/book/listings/g_object_memory_management/1/main.rs +++ b/book/listings/g_object_memory_management/1/main.rs @@ -1,8 +1,6 @@ -use std::cell::Cell; -use std::rc::Rc; - use gtk::prelude::*; -use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation}; +use gtk::{glib, Application, ApplicationWindow, Button}; +use std::cell::Cell; const APP_ID: &str = "org.gtk_rs.GObjectMemoryManagement1"; @@ -16,7 +14,9 @@ fn main() -> glib::ExitCode { // Run the application app.run() } -fn build_ui(app: &Application) { + +// ANCHOR: build_ui +fn build_ui(application: &Application) { // Create two buttons let button_increase = Button::builder() .label("Increase") @@ -25,38 +25,22 @@ fn build_ui(app: &Application) { .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(); - // ANCHOR: callback - // Reference-counted object with inner-mutability - let number = Rc::new(Cell::new(0)); + // A mutable integer + let number = Cell::new(0); - // Connect callbacks, when a button is clicked `number` will be changed - let number_copy = number.clone(); - button_increase.connect_clicked(move |_| number_copy.set(number_copy.get() + 1)); - button_decrease.connect_clicked(move |_| number.set(number.get() - 1)); - // ANCHOR_END: callback - - // 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); + // Connect callbacks + // When a button is clicked, `number` should be changed + button_increase.connect_clicked(move |_| number.set(number.get() + 1)); // Create a window let window = ApplicationWindow::builder() - .application(app) + .application(application) .title("My GTK App") - .child(>k_box) + .child(&button_increase) .build(); // Present the window window.present(); } +// ANCHOR_END: build_ui diff --git a/book/listings/g_object_memory_management/2/main.rs b/book/listings/g_object_memory_management/2/main.rs index 35d335c87101..da82075af8aa 100644 --- a/book/listings/g_object_memory_management/2/main.rs +++ b/book/listings/g_object_memory_management/2/main.rs @@ -1,7 +1,6 @@ use std::cell::Cell; use std::rc::Rc; -use glib::clone; use gtk::prelude::*; use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation}; @@ -34,17 +33,14 @@ fn build_ui(app: &Application) { .margin_end(12) .build(); - // Reference-counted object with inner mutability - let number = Rc::new(Cell::new(0)); - // Connect callbacks - // When a button is clicked, `number` will be changed // ANCHOR: callback - button_increase.connect_clicked(clone!(@strong number => move |_| { - number.set(number.get() + 1); - })); - button_decrease.connect_clicked(move |_| { - number.set(number.get() - 1); - }); + // Reference-counted object with inner-mutability + let number = Rc::new(Cell::new(0)); + + // Connect callbacks, when a button is clicked `number` will be changed + let number_copy = number.clone(); + button_increase.connect_clicked(move |_| number_copy.set(number_copy.get() + 1)); + button_decrease.connect_clicked(move |_| number.set(number.get() - 1)); // ANCHOR_END: callback // Add buttons to `gtk_box` diff --git a/book/listings/g_object_memory_management/3/main.rs b/book/listings/g_object_memory_management/3/main.rs index 507ebd26bcc2..52d68b7652ff 100644 --- a/book/listings/g_object_memory_management/3/main.rs +++ b/book/listings/g_object_memory_management/3/main.rs @@ -17,7 +17,6 @@ fn main() -> glib::ExitCode { // Run the application app.run() } - fn build_ui(app: &Application) { // Create two buttons let button_increase = Button::builder() @@ -35,21 +34,17 @@ fn build_ui(app: &Application) { .margin_end(12) .build(); + // Reference-counted object with inner mutability let number = Rc::new(Cell::new(0)); - - // ANCHOR: callback // Connect callbacks - // When a button is clicked, `number` and label of the other button will be changed - button_increase.connect_clicked(clone!(@weak number, @strong button_decrease => - move |_| { - number.set(number.get() + 1); - button_decrease.set_label(&number.get().to_string()); - })); - button_decrease.connect_clicked(clone!(@strong button_increase => - move |_| { - number.set(number.get() - 1); - button_increase.set_label(&number.get().to_string()); + // When a button is clicked, `number` will be changed + // ANCHOR: callback + button_increase.connect_clicked(clone!(@strong number => move |_| { + number.set(number.get() + 1); })); + button_decrease.connect_clicked(move |_| { + number.set(number.get() - 1); + }); // ANCHOR_END: callback // Add buttons to `gtk_box` diff --git a/book/listings/g_object_memory_management/4/main.rs b/book/listings/g_object_memory_management/4/main.rs index f9aa0e71fe7a..2e09ff0a4223 100644 --- a/book/listings/g_object_memory_management/4/main.rs +++ b/book/listings/g_object_memory_management/4/main.rs @@ -35,41 +35,36 @@ fn build_ui(app: &Application) { .margin_end(12) .build(); - // Reference-counted object with inner mutability let number = Rc::new(Cell::new(0)); // ANCHOR: callback // Connect callbacks // When a button is clicked, `number` and label of the other button will be changed - button_increase.connect_clicked(clone!(@weak number, @weak button_decrease => + button_increase.connect_clicked(clone!(@weak number, @strong button_decrease => move |_| { number.set(number.get() + 1); button_decrease.set_label(&number.get().to_string()); })); - button_decrease.connect_clicked(clone!(@weak button_increase => + button_decrease.connect_clicked(clone!(@strong button_increase => move |_| { number.set(number.get() - 1); button_increase.set_label(&number.get().to_string()); })); // ANCHOR_END: callback - // ANCHOR: box_append // 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); - // ANCHOR_END: box_append - // ANCHOR: window_child // Create a window let window = ApplicationWindow::builder() .application(app) .title("My GTK App") .child(>k_box) .build(); - // ANCHOR_END: window_child // Present the window window.present(); diff --git a/book/listings/g_object_memory_management/5/main.rs b/book/listings/g_object_memory_management/5/main.rs new file mode 100644 index 000000000000..d9553e78562b --- /dev/null +++ b/book/listings/g_object_memory_management/5/main.rs @@ -0,0 +1,76 @@ +use std::cell::Cell; +use std::rc::Rc; + +use glib::clone; +use gtk::prelude::*; +use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation}; + +const APP_ID: &str = "org.gtk_rs.GObjectMemoryManagement5"; + +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 number = Rc::new(Cell::new(0)); + + // ANCHOR: callback + // Connect callbacks + // When a button is clicked, `number` and label of the other button will be changed + button_increase.connect_clicked(clone!(@weak number, @weak button_decrease => + move |_| { + number.set(number.get() + 1); + button_decrease.set_label(&number.get().to_string()); + })); + button_decrease.connect_clicked(clone!(@weak button_increase => + move |_| { + number.set(number.get() - 1); + button_increase.set_label(&number.get().to_string()); + })); + // ANCHOR_END: callback + + // ANCHOR: box_append + // 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); + // ANCHOR_END: box_append + + // ANCHOR: window_child + // Create a window + let window = ApplicationWindow::builder() + .application(app) + .title("My GTK App") + .child(>k_box) + .build(); + // ANCHOR_END: window_child + + // Present the window + window.present(); +} diff --git a/book/src/g_object_memory_management.md b/book/src/g_object_memory_management.md index 6e7e62d92643..0819019163fb 100644 --- a/book/src/g_object_memory_management.md +++ b/book/src/g_object_memory_management.md @@ -1,14 +1,18 @@ # Memory Management -A GObject (or [`glib::Object`](https://gtk-rs.org/gtk-rs-core/stable/latest/docs/glib/object/struct.Object.html) in Rust terms) is a reference-counted, mutable object. -Let's see in a set of real life examples which consequences this has. +Memory management when writing a gtk-rs app can be a bit tricky. +Let's have a look why that is the case and how to deal with that. + +With our first example, we have window with a single button. +Every button click should increment an integer `number` by one. ```rust ,no_run,compile_fail #use gtk::prelude::*; -#use gtk::{self, glib, Application, ApplicationWindow, Button, Orientation}; +#use gtk::{self, glib, Application, ApplicationWindow, Button}; # #const APP_ID: &str = "org.gtk_rs.GObjectMemoryManagement0"; # +// DOES NOT COMPILE! fn main() -> glib::ExitCode { // Create a new application let app = Application::builder().application_id(APP_ID).build(); @@ -29,13 +33,6 @@ fn build_ui(application: &Application) { .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(); // A mutable integer let mut number = 0; @@ -43,20 +40,12 @@ fn build_ui(application: &Application) { // Connect callbacks // When a button is clicked, `number` should be changed button_increase.connect_clicked(|_| number += 1); - button_decrease.connect_clicked(|_| number -= 1); - - // 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(application) .title("My GTK App") - .child(>k_box) + .child(&button_increase) .build(); // Present the window @@ -64,75 +53,122 @@ fn build_ui(application: &Application) { } ``` -Here we would like to create a simple app with two buttons. -If we click on one button, an integer number should be increased. If we press the other one, it should be decreased. -The Rust compiler refuses to compile it though. - -For once the borrow checker kicked in: - -```console -error[E0499]: cannot borrow `number` as mutable more than once at a time - --> main.rs:27:37 - | -26 | button_increase.connect_clicked(|_| number += 1); - | ------------------------------------------------ - | | | | - | | | first borrow occurs due to use of `number` in closure - | | first mutable borrow occurs here - | argument requires that `number` is borrowed for `'static` -27 | button_decrease.connect_clicked(|_| number -= 1); - | ^^^ ------ second borrow occurs due to use of `number` in closure - | | - | second mutable borrow occurs here -``` - -Also, the compiler tells us that our closures may outlive `number`: +The Rust compiler refuses to compile this application while spitting our multiple error messages. +Let's have a look at them one by one. ```console error[E0373]: closure may outlive the current function, but it borrows `number`, which is owned by the current function - --> main.rs:26:37 | -26 | button_increase.connect_clicked(|_| number += 1); +32 | button_increase.connect_clicked(|_| number += 1); | ^^^ ------ `number` is borrowed here | | | may outlive borrowed value `number` | note: function requires argument type to outlive `'static` - --> main.rs:26:5 | -26 | button_increase.connect_clicked(|_| number += 1); +32 | button_increase.connect_clicked(|_| number += 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: to force the closure to take ownership of `number` (and any other referenced variables), use the `move` keyword | -26 | button_increase.connect_clicked(move |_| number += 1); - | ^^^^^^^^ +32 | button_increase.connect_clicked(move |_| number += 1); + | ``` -Thinking about the second error message, it makes sense that the closure requires the lifetimes of references to be `'static`. -The compiler cannot know when the user presses a button, so references must live forever. -And our `number` gets immediately deallocated after it reaches the end of its scope. -The error message is also suggesting that we could take ownership of `number`. -But is there actually a way that both closures could take ownership of the same value? +Our closure only borrows `number`. +Signal handlers in GTK require `static'` lifetimes for their references, so we cannot borrow a variable that only lives for the scope of the function `build_ui`. +The compiler also suggests how to fix this. +By adding the `move` keyword in front of the closure, `number` will be moved into the closure. -Yes! That is exactly what the [`Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html) type is there for. -The `Rc` counts the number of strong references created via [`Clone::clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html#tymethod.clone) and released via [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop), and only deallocates it when this number drops to zero. -We call every object containing a strong reference a shared owner of the value. -If we want to modify the content of our [`Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html), -we can use the [`Cell`](https://doc.rust-lang.org/std/cell/struct.Cell.html) type. +```rust ,no_run,compile_fail +#use gtk::prelude::*; +#use gtk::{self, glib, Application, ApplicationWindow, Button}; +# +#const APP_ID: &str = "org.gtk_rs.GObjectMemoryManagement0"; +# +#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(application: &Application) { +# // Create two buttons +# let button_increase = Button::builder() +# .label("Increase") +# .margin_top(12) +# .margin_bottom(12) +# .margin_start(12) +# .margin_end(12) +# .build(); +# + // DOES NOT COMPILE! + // A mutable integer + let mut number = 0; + + // Connect callbacks + // When a button is clicked, `number` should be changed + button_increase.connect_clicked(move |_| number += 1); +# +# // Create a window +# let window = ApplicationWindow::builder() +# .application(application) +# .title("My GTK App") +# .child(&button_increase) +# .build(); +# +# // Present the window +# window.present(); +#} +``` + +This still leaves the following error message: + +```console + +error[E0594]: cannot assign to `number`, as it is a captured variable in a `Fn` closure + | +32 | button_increase.connect_clicked(move |_| number += 1); + | ^^^^^^^^^^^ cannot assign +``` + +In order to understand that error message we have to understand the difference between the three closure traits `FnOnce`, `FnMut` and `Fn`. +APIs that take closures implementing the `FnOnce` trait give the most freedom to the API consumer. +The closure is called only once, so it can even consume its state. +Signal handlers can be called multiple times, so they cannot accept `FnOnce`. + +The more restrictive `FnMut` trait doesn't allow closures to consume their state, but they can still mutate it. +Signal handlers can't allow this either, because they can be called from inside themselves. +This would lead to multiple mutable references which the borrow checker doesn't appreciate at all. + +This leaves `Fn`. +State can be immutably borrowed, but then how can we modify `number`? +We need a data type with interior mutability like [`std::cell::Cell`](https://doc.rust-lang.org/std/cell/struct.Cell.html). > The `Cell` class is only suitable for objects that implement the [`Copy`](https://doc.rust-lang.org/core/marker/trait.Copy.html) trait. > For other objects, [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) is the way to go. > You can learn more about interior mutability in this [section](https://marabos.nl/atomics/basics.html#interior-mutability) of the book _Rust Atomics and Locks_. -Filename: listings/g_object_memory_management/1/main.rs +Filename: listings/g_object_memory_management/1/main.rs ```rust ,no_run,noplayground -{{#rustdoc_include ../listings/g_object_memory_management/1/main.rs:callback}} +{{#rustdoc_include ../listings/g_object_memory_management/1/main.rs:build_ui}} ``` -It is not very nice though to fill the scope with temporary variables like `number_copy`. -We can improve that by using the [`glib::clone!`](https://gtk-rs.org/gtk-rs-core/stable/latest/docs/glib/macro.clone.html) macro. +This now compiles as expected. +Let's try a slightly more complicated example: two buttons which both modify the same `number`. +For that, we need a way that both closures take ownership of the same value? + +That is exactly what the [`std::rc::Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html) type is there for. +`Rc` counts the number of strong references created via [`Clone::clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html#tymethod.clone) and released via [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop), and only deallocates the value when this number drops to zero. +If we want to modify the content of our [`Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html), +we can again use the [`Cell`](https://doc.rust-lang.org/std/cell/struct.Cell.html) type. + Filename: listings/g_object_memory_management/2/main.rs @@ -140,8 +176,8 @@ Filename: listings/g_object_memory_management/3/main.rs @@ -149,6 +185,15 @@ Filename: listings/g_object_memory_management/4/main.rs + +```rust ,no_run,noplayground +{{#rustdoc_include ../listings/g_object_memory_management/4/main.rs:callback}} +``` + If we now click on one button, the other button's label gets changed. But whoops! @@ -160,10 +205,10 @@ If this chain leads to a circle, none of the values in this cycle ever get deall With weak references we can break this cycle, because they don't keep their value alive but instead provide a way to retrieve a strong reference if the value is still alive. Since we want our apps to free unneeded memory, we should use weak references for the buttons instead. -Filename: listings/g_object_memory_management/4/main.rs +Filename: listings/g_object_memory_management/5/main.rs ```rust ,no_run,noplayground -{{#rustdoc_include ../listings/g_object_memory_management/4/main.rs:callback}} +{{#rustdoc_include ../listings/g_object_memory_management/5/main.rs:callback}} ``` The reference cycle is broken. @@ -177,18 +222,18 @@ If we had moved weak references in both closures, nothing would have kept `numbe Thinking about this, `button_increase` and `button_decrease` are also dropped at the end of the scope of `build_ui`. Who then keeps the buttons alive? -Filename: listings/g_object_memory_management/4/main.rs +Filename: listings/g_object_memory_management/5/main.rs ```rust ,no_run,noplayground -{{#rustdoc_include ../listings/g_object_memory_management/4/main.rs:box_append}} +{{#rustdoc_include ../listings/g_object_memory_management/5/main.rs:box_append}} ``` When we append the buttons to the `gtk_box`, `gtk_box` keeps a strong reference to them. -Filename: listings/g_object_memory_management/4/main.rs +Filename: listings/g_object_memory_management/5/main.rs ```rust ,no_run,noplayground -{{#rustdoc_include ../listings/g_object_memory_management/4/main.rs:window_child}} +{{#rustdoc_include ../listings/g_object_memory_management/5/main.rs:window_child}} ``` When we set `gtk_box` as child of `window`, `window` keeps a strong reference to it. diff --git a/book/src/main_event_loop.md b/book/src/main_event_loop.md index 21af76bc72c7..08c3090cdb80 100644 --- a/book/src/main_event_loop.md +++ b/book/src/main_event_loop.md @@ -136,8 +136,7 @@ But why did we not do the same thing with our multi-threaded example? # .margin_end(12) # .build(); # - // DOES NOT COMPILE - + // DOES NOT COMPILE! // Connect to "clicked" signal of `button` button.connect_clicked(move |button| { button.clone();