Как переместить данные в несколько Rust-замыканий? - PullRequest
0 голосов
/ 23 сентября 2018

У меня есть два виджета в простом приложении 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`

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

Ответы [ 3 ]

0 голосов
/ 23 сентября 2018

Как объяснено в ответе Шепмастера, вы можете перемещать значение из переменной только один раз, и компилятор не позволит вам сделать это во второй раз.Я постараюсь добавить немного конкретного контекста для этого варианта использования.Большая часть этого написана на моей памяти об использовании 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.Более того, нам нужен только доступ для чтения к 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();

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

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

0 голосов
/ 08 декабря 2018

Вы можете использовать клонирование в виджетах 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: Обратные вызовы и замыкания

0 голосов
/ 23 сентября 2018

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

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

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

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

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

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

См. Также:

...