从一个循环中切换一个 bool 和 Fn losure with rust and gtk

Toggling a bool from within a loop and Fn losure with rust and gtk

我正在尝试用 rust 和 gtk 制作一个井字游戏,为了交换回合,我在单击游戏按钮时切换 bool。由于 rust/gtk 集成,Fn 闭包中的 onclick 事件 运行,所以我无法直接编辑对 bool 的可变引用。

基于this post,我从一个基本的 bool 移动到一个单元格内的 bool,因为它具有内部可变性,允许它在闭包内变化。

然而,变量被移动到闭包中,并且在 xturn 的 loop/usage 的下一次迭代中我得到

--> 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 

下面的代码

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.");
            }
            
        });

    }
}

通过添加 & 导致相同的初始错误消息来创建对 xturn 的每次使用的引用

在闭包开始时取消移动会导致此错误

error[E0373]: closure may outlive the current function, but it borrows `xturn`, which is owned by the current function

完整代码

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: &gtk::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
}

如编译器所示,这两种方法都有缺陷。

  • 当您使用 move 方法时,您是在告诉闭包它可以有 xturn,但由于这是一个循环,它可以执行多次。您不能将相同的值移动两次(因为它在第一次后就消失了),因此不允许第二次移动。
  • 当您尝试通过引用捕获时,编译器会告诉您 xturn 属于声明它的函数,但是您在循环中创建的闭包将比该函数更有效,因此引用将失效。

看起来你想要 共享所有权 单个 bool,它在 Rust 中由 Rc 结构实现,它执行 reference-counting 来确定什么时候可以销毁共享值。

然而,Rc 不允许可变借用,而同一共享值存在多个 Rc,因此您仍然需要 Cell 的内部可变性。您要使用的最终类型是 Rc<Cell<bool>>。 (此处不需要 RefCell,因为您只需要能够获取和设置 bool 值;您永远不需要对它的引用。)

您需要循环的每次迭代来移动 Rc 的副本。每个单独的 Rc 值将引用相同的 Cell。例如,这个函数 returns 9 个闭包都做同样的事情:它们切换共享布尔值和 return 它在一个元组中连同它们自己的索引:

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()
}

(Playground)