Как переместить данные в несколько 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`

Что я могу сделать?

2 ответа

Как объяснено в ответе Shepmaster, вы можете перемещать значение из переменной только один раз, и компилятор не позволит вам сделать это во второй раз. Я постараюсь добавить немного конкретного контекста для этого варианта использования. Большая часть этого написана на моей памяти об использовании GTK из C давным-давно, и несколько раз я только что посмотрел в документации по 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 обработчик имеет черту, связанную Fn(&Self) + 'staticЭто означает, что замыкание либо не может содержать никаких ссылок вообще, либо все ссылки, которые оно содержит, должны иметь статическое время жизни. Если мы не будем двигаться list_box в закрытии, закрытие будет содержать нестатическую ссылку на него. Поэтому нам нужно избавиться от ссылки, прежде чем мы сможем использовать функцию в качестве обработчика событий.

Почему gtk-rs навязывает это ограничение? Причина в том, что gtk-rs является оболочкой для набора библиотек C, и указатель на обратный вызов в конечном итоге передается в базовый glib библиотека. Поскольку в C нет понятия времени жизни, единственный способ сделать это безопасно - потребовать, чтобы не было ссылок, которые могут стать недействительными.

Теперь мы установили, что наши закрытия не могут содержать никаких ссылок. Нам все еще нужно получить доступ list_box из замыканий, так, каковы наши варианты? Если у вас есть только одно закрытие, используя move делает трюк - двигаясь list_box в закрытие, закрытие становится его владельцем. Однако мы видели, что это не работает для более чем одного замыкания, потому что мы можем только двигаться list_box один раз. Нам нужно найти способ иметь несколько владельцев для него, и стандартная библиотека Rust предоставляет такой способ: указатели подсчета ссылок Rc а также Arc, Первый используется для значений, доступ к которым возможен только из текущего потока, а второй безопасно для перемещения в другие потоки.

Если я правильно помню, glib выполняет все обработчики событий в основном потоке, и границы черт для замыкания отражают это: замыкание не обязательно должно быть Send или же 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();

Теперь у вас есть два "собственных" указателя на один и тот же список, и эти указатели можно переместить в два замыкания.

Отказ от ответственности: я не мог действительно протестировать ничего из этого, так как ваш пример кода не является автономным.

Вы можете использовать клонирование в виджетах gtk-rs.

В gtk-rs каждый объект реализует gtk::Widget (так что в основном каждый объект GTK вы можете использовать внутри gtk::Window) также необходимо реализовать Clone черта характера. призвание 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: Обратные вызовы и замыкания

Вы буквально не можете сделать это. Я рекомендую вам вернуться и перечитать Rust Programming Language, чтобы освежить в себе чувство собственности. Когда неCopy тип перемещен, он исчез - это гигантская причина того, что Rust даже существует: отслеживать это, чтобы программисту не пришлось это делать.

Если тип Copy, компилятор автоматически сделает копию для вас. Если тип Clone, то вы должны явно вызвать клон.

Вам нужно будет перейти в общее владение и, скорее всего, изменчивость интерьера.

Совместное владение позволяет единому фрагменту данных совместно владеть несколькими значениями, создавая дополнительных владельцев посредством клонирования.

Внутренняя изменчивость необходима, потому что Rust запрещает множественные изменяемые ссылки на один элемент одновременно.

Оберните list_box в Mutex а затем Arc (Arc<Mutex<T>>). Клонировать Arc для каждого обработчика и переместите этот клон в обработчик. Затем вы можете заблокировать list_box и внесите все необходимые изменения.

Смотрите также:

Другие вопросы по тегам