如何将数据移动到多个Rust闭包中?

J. *_*Doe 4 rust gtk-rs

我在一个简单的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();
}
Run Code Online (Sandbox Code Playgroud)

我需要更改list_box两个事件。我有两个闭包that move,但是list_box由于出现错误,无法同时移动到两个闭包:

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();
}
Run Code Online (Sandbox Code Playgroud)

我能做什么?

Sve*_*ach 7

如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
Run Code Online (Sandbox Code Playgroud)

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

为什么gtk-rs会施加此限制?原因是gtk-rs是一组C库的包装,并且指向回调的指针最终传递给了基础glib库。由于C没有任何生存期概念,因此安全地执行此操作的唯一方法是要求不存在任何可能成为无效的引用。

现在,我们已经确定闭包不能包含任何引用。我们仍然需要list_box从闭包访问,那么我们有什么选择呢?如果您只有一个封闭,则可以使用move技巧–通过移入list_box封闭,封闭成为其所有者。但是,我们已经看到,此操作不适用于多个闭包,因为我们只能移动list_box一次。我们需要找到一种拥有多个所有者的方法,Rust标准库提供了这样一种方法:引用计数指针RcArc。前者用于只能从当前线程访问的值,而后者可以安全地移至其他线程。

如果我没记错的话,glib在主线程中执行所有事件处理程序,并且闭包的特征范围反映了这一点:闭包不是必须为Sendor Sync,所以我们应该可以使用Rc。Morevoer,我们只需要list_box对闭包进行读取访问,因此在这种情况下,我们不需要RefCell或不需要Mutex内部可变性。总之,您所需要做的只是:

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();
Run Code Online (Sandbox Code Playgroud)

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

免责声明:由于您的示例代码不是独立的,因此我无法真正测试其中的任何一个。


Hol*_*bor 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();
});
Run Code Online (Sandbox Code Playgroud)

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

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
        }
    );
}
Run Code Online (Sandbox Code Playgroud)

用法很简单:

search_entry.connect_search_changed(clone!(list_box => move |_se| {
    let _a = list_box.get_selected_rows();
}));
Run Code Online (Sandbox Code Playgroud)

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

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


She*_*ter 3

你确实不能这样做。我鼓励您回去重新阅读Rust 编程语言,以刷新自己的所有权。当非类型Copy移动时,它就消失了 \xe2\x80\x94\xc2\xa0 这是 Rust 存在的一个重要原因:跟踪它,这样程序员就不必这样做。

\n\n

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

\n\n

您将需要更改为共享所有权,并且很可能需要更改内部可变性

\n\n

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

\n\n

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

\n\n

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

\n\n

也可以看看:

\n\n\n