6

我在一个简单的 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`

我能做些什么?

4

3 回答 3

8

正如 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();

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

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

于 2018-09-23T20:47:31.113 回答
7

您可以在 gtk-rs 小部件上使用克隆。

在 gtk-rs 中,每个实现的对象gtk::Widget(基本上你可以在 a 中使用的每个 GTK 对象gtk::Window)也必须实现Clonetrait。调用clone()非常便宜,因为它只是一个指针副本和一个引用计数器更新。

了解以下内容是有效且便宜的:

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

但是由于这个解决方案很冗长,如果你有多个对象要移动,很快就会变得非常难看,社区提出了以下宏:

macro_rules! clone {
    (@param _) => ( _ );
    (@param $x:ident) => ( $x );
    ($($n:ident),+ => move || $body:expr) => (
        {
            $( let $n = $n.clone(); )+
            move || $body
        }
    );
    ($($n:ident),+ => move |$($p:tt),+| $body:expr) => (
        {
            $( let $n = $n.clone(); )+
            move |$(clone!(@param $p),)+| $body
        }
    );
}

用法很简单:

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

这个宏能够克隆任意数量的移动到闭包中的对象。

有关进一步的解释和示例,请查看 gtk-rs 团队的本教程:回调和闭包

于 2018-12-08T13:36:27.320 回答
3

你真的不能这样做。我鼓励您返回并重新阅读Rust 编程语言,以刷新自己的所有权。当一个非Copy类型被移动时,它就消失了——这是 Rust 甚至存在的一个重要原因:跟踪它,这样程序员就不必这样做了。

如果类型是Copy,编译器会自动为您制作副本。如果类型是Clone,那么您必须显式调用克隆。

您将需要更改为共享所有权和最有可能的内部可变性

共享所有权允许单个数据由多个值共同拥有,通过克隆创建额外的所有者。

需要内部可变性是因为 Rust 不允许同时对一个项目进行多个可变引用。

list_box用 aMutexArc( Arc<Mutex<T>>)包裹你。克隆Arc每个处理程序并将该克隆移动到处理程序中。然后,您可以锁定list_box并进行所需的任何更改。

也可以看看:

于 2018-09-23T16:06:43.410 回答