首页 > 解决方案 > 如何将数据移动到多个 Rust 闭包中?

问题描述

我在一个简单的 GTK 应用程序中有两个小部件:

extern crate gdk;
extern crate gtk;

use super::desktop_entry::DesktopEntry;

use gdk::enums::key;
use gtk::prelude::*;

pub fn launch_ui(_desktop_entries: Vec<DesktopEntry>) {
    gtk::init().unwrap();

    let builder = gtk::Builder::new_from_string(include_str!("interface.glade"));

    let window: gtk::Window = builder.get_object("main_window").unwrap();
    let search_entry: gtk::SearchEntry = builder.get_object("search_entry").unwrap();
    let list_box: gtk::ListBox = builder.get_object("list_box").unwrap();

    window.show_all();

    search_entry.connect_search_changed(move |_se| {
        let _a = list_box.get_selected_rows();
    });

    window.connect_key_press_event(move |_, key| {
        match key.get_keyval() {
            key::Down => {
                list_box.unselect_all();
            }
            _ => {}
        }
        gtk::Inhibit(false)
    });

    gtk::main();
}

我需要改变list_box这两个事件。我有两个闭包,但是当我得到错误时,move不可能同时移动到两个闭包:list_box

error[E0382]: capture of moved value: `list_box`

我能做些什么?

标签: rustgtk-rs

解决方案


正如 Shepmaster 的回答中所解释的,您只能将一个值从变量中移出一次,编译器会阻止您再次执行此操作。我将尝试为这个用例添加一些特定的上下文。其中大部分来自我很久以前使用过 C 的 GTK 的记忆,并且我刚刚在 gtk-rs 文档中查找了一些内容,所以我确定我有一些细节错误,但我认为一般要点是准确的.

让我们首先看一下为什么首先需要将值移动到闭包中。list_box您在两个闭包中调用的方法self通过引用获取,因此您实际上不会使用闭包中的列表框。这意味着在没有说明符的情况下定义两个闭包是完全有效的move——您只需要对 的只读引用list_box,您可以一次拥有多个只读引用,并且list_box至少与闭包一样长。

然而,虽然你可以在不进入它们的情况下定义两个闭包list_box,但你不能将这样定义的闭包传递给 gtk-rs:所有连接事件处理程序的函数只接受“静态”函数,例如

fn connect_search_changed<F: Fn(&Self) + 'static>(
    &self, 
    f: F
) -> SignalHandlerId

处理程序的类型F具有特征 bound Fn(&Self) + 'static,这意味着闭包根本不能保存任何引用,或者它保存的所有引用都必须具有静态生命周期。如果我们不list_box进入闭包,闭包将持有对它的非静态引用。所以我们需要在能够将函数用作事件处理程序之前摆脱引用。

为什么 gtk-rs 会施加这个限制?原因是 gtk-rs 是一组 C 库的包装器,指向回调的指针最终会传递给底层glib库。由于 C 没有任何生命周期的概念,因此安全地执行此操作的唯一方法是要求没有任何可能变为无效的引用。

我们现在已经确定我们的闭包不能保存任何引用。我们仍然需要list_box从闭包中访问,那么我们有什么选择呢?如果你只有一个闭包,那么 usingmove就可以了——通过移动list_box到闭包中,闭包成为它的所有者。然而,我们已经看到这对于不止一个闭包不起作用,因为我们只能移动list_box一次。我们需要找到一种方法来拥有多个所有者,Rust 标准库提供了这样一种方法:引用计数指针RcArc. 前者用于仅从当前线程访问的值,而后者可以安全地移动到其他线程。

如果我没记错的话,glib 在主线程中执行所有事件处理程序,并且闭包的 trait bound 反映了这一点:闭包不需要是Sendor Sync,所以我们应该可以使用Rc. 此外,我们只需要list_box在闭包中读取访问权限,因此在这种情况下我们不需要RefCellMutex内部可变性。总而言之,您所需要的可能就是:

use std::rc::Rc;
let list_box: gtk::ListBox = builder.get_object("list_box").unwrap();
let list_box_1 = Rc::new(list_box);
let list_box_2 = list_box_1.clone();

现在你有两个指向同一个列表框的“拥有”指针,这些指针可以移动到两个闭包中。

免责声明:我无法真正测试任何这些,因为您的示例代码不是独立的。


推荐阅读