I'm trying to make a tic tac toe game with rust and gtk, and in order to swap turns I'm toggling bool when a game button is clicked. Because of the rust/gtk integration, the onclick events run within an Fn closure, so I can't directly edit a mutable reference to the bool.
Based on this post, I moved from a basic bool to a bool within a cell as that has interior mutability, allowing it to be varied from within the closure.
However, the variable gets moved into the closure, and in the next iteration of the loop/usage of xturn I get
--> src/main.rs:45:44
|
39 | let xturn = Cell::new(true);
| ----- move occurs because `xturn` has type `std::cell::Cell<bool>`, which does not implement the `Copy` trait
...
45 | current_button.connect_clicked(move |current_button|{
| ^^^^^^^^^^^^^^^^^^^^^ value moved into closure here, in previous iteration of loop
46 | if current_button.label().unwrap() == ""{
47 | if xturn.get() {
| ----- use occurs due to use in closure
Code below
let game_grid: [[Button; 3];3] = [[new_game_button(), new_game_button(), new_game_button()],[new_game_button(), new_game_button(), new_game_button()],[new_game_button(), new_game_button(), new_game_button()]];
let xturn = Cell::new(true);
for i in 0..3{
for o in 0..3{
let current_button = &game_grid[i][o];
grid.attach(current_button, i.try_into().unwrap(), o.try_into().unwrap(), 1, 1);
current_button.connect_clicked(move |current_button|{
if current_button.label().unwrap() == ""{
if xturn.get() {
current_button.set_label("X");
} else{
current_button.set_label("O");
};
xturn.set(!xturn.get());
} else{
println!("This spot is taken! Go Again.");
}
});
}
}
Creating a reference to each use of xturn by adding & results in the same initial error message
Getting rid of the move at the start of the closure results in this error
error[E0373]: closure may outlive the current function, but it borrows `xturn`, which is owned by the current function
Complete code
use std::cell::Cell;
use gtk4 as gtk;
use gtk::{prelude::*, Label};
use gtk::{Align, Application, ApplicationWindow, Button, Grid};
fn main() {
let app = Application::builder()
.application_id("org.beribus.tictactoe")
.build();
app.connect_activate(build_ui);
println!("Hello, world!");
app.run();
}
fn build_ui(app: >k::Application){
let window = ApplicationWindow::builder()
.application(app)
.default_width(360)
.default_height(360)
.title("GTK Tac Toe")
.build();
let grid = Grid::builder()
.margin_top(10)
.margin_bottom(10)
.margin_start(10)
.margin_end(10)
.halign(gtk::Align::Center)
.valign(gtk::Align::Center)
.row_spacing(6)
.column_spacing(6)
.build();
window.set_child(Some(&grid));
let game_grid: [[Button; 3];3] = [[new_game_button(), new_game_button(), new_game_button()],[new_game_button(), new_game_button(), new_game_button()],[new_game_button(), new_game_button(), new_game_button()]];
let xturn = Cell::new(true);
for i in 0..3{
for o in 0..3{
let current_button = &game_grid[i][o];
grid.attach(current_button, i.try_into().unwrap(), o.try_into().unwrap(), 1, 1);
current_button.connect_clicked(move |current_button|{
if current_button.label().unwrap() == ""{
if xturn.get() {
current_button.set_label("X");
} else{
current_button.set_label("O");
};
&xturn.set(!&xturn.get());
} else{
println!("This spot is taken! Go Again.");
}
});
}
}
let text = Label::builder()
.label("woro")
.build();
if xturn.get(){
text.set_label("yoyooyo it's X's turn");
} else{
text.set_label("yoyooyo it's O's turn");
}
grid.attach(&text, 0,4,3,1);
let reset_button = Button::builder()
.halign(Align::Center)
.valign(Align::Center)
.label("Reset Game")
.build();
reset_button.connect_clicked(move |_|{
for i in 0..3{
for o in 0..3{
let current_button = &game_grid[i][o];
current_button.set_label("");
}
}
});
grid.attach(&reset_button, 0,3,3,1);
window.show();
}
fn new_game_button() -> Button{
let button = Button::builder()
.halign(Align::Center)
.valign(Align::Center)
.label("")
.width_request(90)
.height_request(90)
.build();
button
}
Both approaches are flawed, as the compiler indicated.
move
approach, you are telling the closure that it can have xturn
, but since this is in a loop, it can execute multiple times. You can't move the same value twice (since it is gone after the first time) and so the second move is disallowed.xturn
belongs to the function in which it is declared, but the closures you are creating in the loop will outlive the function, and therefore the references will become invalid.It looks like you want shared ownership of a single bool, which is implemented in Rust by the Rc
struct, which performs reference-counting to determine when the shared value can be destroyed.
However, Rc
does not permit mutable borrows while more than one Rc
exists for the same shared value, so you still need the interior mutability of Cell
. The final type that you want to use is Rc<Cell<bool>>
. (RefCell
isn't required here since you only need to be able to get and set the bool value; you don't ever need a reference to it.)
You need each iteration of the loop to move a copy of the Rc
. Each separate Rc
value will refer to the same Cell
. For example, this function returns 9 closures that all do the same thing: they toggle the shared boolean value and return it in a tuple along with their own index:
fn create_closures() -> Vec<Box<dyn Fn() -> (i32, bool)>> {
let xturn = Rc::new(Cell::new(false));
(0..9).map(|i| -> Box<dyn Fn() -> (i32, bool)> {
// Make a clone of the Rc that this closure can steal.
let xturn = xturn.clone();
Box::new(move || {
let v = !xturn.get();
xturn.set(v);
(i, v)
})
}).collect()
}