1

Тема: Зміна значення self змінної у функції замикання

Є self змінна, що належить певній структурі.
При побудові структури цій змінній надається початкове значення.
Тоді за допомогою трейту .connect_changed,
що відстежує зміни в налаштуваннях програми,
потрібно надати self змінній нового значення, але всі дії відбуваються в замиканні.
Замикання нічого не повертає, будь-які змінні, що потрапляють всередину, там і лишаються.
Що робити в такому разі?

2

Re: Зміна значення self змінної у функції замикання

Опис якийсь незрозумілий, але дуже тхне Arc<RefCell<>>.

3

Re: Зміна значення self змінної у функції замикання

koala написав:

Опис якийсь незрозумілий, але дуже тхне Arc<RefCell<>>.

У змінній знаходиться вміст текстових файлів, при відкритті нового файлу треба цей вміст оновлювати.
Arc — це ж unsafe, хотілося б без нього обійтися.

4

Re: Зміна значення self змінної у функції замикання

Так, я за компом. Починаємо з початку. Ви, схоже, чомусь дуже намагаєтеся економити власний час на описі проблеми - а це не веде до економії часу, бо нічого не зрозуміло (і передусім - вам).

Teg Miles написав:

Є self змінна, що належить певній структурі.

self - це ключове слово, що позначає перший параметр методу (об'єкт, для якого метод викликаний). Яким чином окремий параметр може бути змінною, що належить певній структурі? У вас є змінна в структурі, що зветься r#self, щоб дозволити використання ключового слова як звичайного ідентифікатора? Чи не належить, а відноситься до типу, який є структурою? Чи ще щось?

Teg Miles написав:

При побудові структури цій змінній надається початкове значення.

Ага, варіант з аргументом відпадає. Ви self побудувати не можете, він уже передається. Чи ви маєте на увазі, що десь колись цій змінній надається початкове значення, а у вашому коді треба із ним працювати? Тоді нащо про це писати? Очевидно, що змінна буде десь ініціалізована. Особливо в Rust, бо неініціалізована змінна - це помилка.

Teg Miles написав:

Тоді за допомогою трейту .connect_changed,

Дуже дивна назва для трейту. У Rust трейти називаються ВерблюдячимРегістром, з великих літер і без підкреслення. Ви мали на увазі, що структура, про яку йде мова, реалізує трейт EditableSignals, в якому є метод connect_changed? Чи щось інше?

Teg Miles написав:

Arc — це ж unsafe, хотілося б без нього обійтися.

І відколи ж це Arc став unsafe? Ні, там є кілька unsafe методів, але і в Vec є unsafe методи. Ви можете спокійно писати код з Arc без unsafe.

Подякували: leofun011

5

Re: Зміна значення self змінної у функції замикання

Це я досі з gtk4 працюю.
Ось ініціалізація екземпляра структури, про яку мова:

pub fn set_main_window(app: &gtk::Application){
    //Створення структури головного вікна та її відображення
    load_images();
    let app_window = gtk::ApplicationWindow::builder()
        .application(app)
        .title("Movar")
        .icon_name("movar")
        .default_width(800)
        .default_height(600)
        .show_menubar(true)
        .build();

    let new_window = Rc::new(app_window.clone());
    let new_dictgroup_dropdown = create_dictgroup_dropdown();
    let new_play_word_button = create_play_word_button();
    let new_settings = get_settings();
    let new_fluent_bundle = get_fluent_bundle(&new_settings, new_window.clone());
    let new_dict_group_stringlist = gtk::StringList::new(Vec::new().as_slice());
    let new_full_dict_indexes = get_full_dict_indexes(&new_window);

    let main_window = MainWindow{
        app: app.clone(),
        window: app_window,
        settings: new_settings,
        main_box: gtk::Box::new(gtk::Orientation::Vertical, 5),
        toolbar_box: gtk::Box::new(gtk::Orientation::Horizontal, 0),
        dictgroup_dropdown: new_dictgroup_dropdown,
        stringlist_model: new_dict_group_stringlist,
        search_entry: gtk::Entry::new(),
        play_word_button: new_play_word_button,
        search_history_listbox: gtk::ListBox::new(),
        search_history_frame: gtk::Frame::new(None),
        web_view: WebView::new(),
        fluent_bundle: new_fluent_bundle,
        full_dict_indexes: new_full_dict_indexes,
    };
    main_window.build_main_window();
    main_window.window.present();
}

А ось це прив'язка до зміни налаштувань, на Rc(RefCell) не зважайте, то я експериментував:

let default_dict_paths_name = "current-dict-path".to_string();
        let full_dict_indexes = Rc::new(RefCell::new(self.full_dict_indexes.clone()));
        let new_window = Rc::new(self.window.clone());
        let settings = self.settings.clone();
        self.settings.connect_changed(
            Some(&default_dict_paths_name), move |_, _| {
                 let default_dict_paths: String =
                    settings.string("current-dict-path").to_string();
                if default_dict_paths != ""{
                    reload_dict_indexes(full_dict_indexes.clone(), &new_window, &settings);
                    for (dict_name, (lang_pair, dict_descr, _, _, _)) in &*full_dict_indexes.borrow(){
                            println!("{}({})", dict_name.to_string(), dict_descr.to_string());
                        }
                }
            });

Я можу запустити звичайну функцію без self всередині замикання, щоб там щось зробити,
але не можу змінити значення self.full_dict_indexes, бо замикання не повертає значень.

6

Re: Зміна значення self змінної у функції замикання

Teg Miles написав:

але не можу змінити значення self.full_dict_indexes

Так а нащо ви його клонуєте?
Ви хочете мати доступ до self.full_dict_indexes з кількох місць. Для цього ви робите його (саме його!) Rc<RefCell> (чи Arc, якщо очікуєте, що буде багатопотоковість). А далі

let full_dict_indexes = Rc::clone(self.full_dict_indexes); //клон саме Rc
...
move |_, _| {
    if let Ok(Some(full_dict_indexes)) = full_dict_indexes.get_mut().map(|value|value.try_borrow_mut()) { //якось так
        //в цьому блоці full_dict_indexes буде розгорнутим
    }
Подякували: Teg Miles, leofun012

7

Re: Зміна значення self змінної у функції замикання

koala написав:
Teg Miles написав:

але не можу змінити значення self.full_dict_indexes

Так а нащо ви його клонуєте?
Ви хочете мати доступ до self.full_dict_indexes з кількох місць. Для цього ви робите його (саме його!) Rc<RefCell> (чи Arc, якщо очікуєте, що буде багатопотоковість). А далі

let full_dict_indexes = Rc::clone(self.full_dict_indexes); //клон саме Rc
...
move |_, _| {
    if let Ok(Some(full_dict_indexes)) = full_dict_indexes.get_mut().map(|value|value.try_borrow_mut()) { //якось так
        //в цьому блоці full_dict_indexes буде розгорнутим
    }

Мені не просто доступ потрібен, потрібно надати нового значення цій змінній в замиканні,
і щоб це нове значення збереглося після виходу із замикання.

8

Re: Зміна значення self змінної у функції замикання

Це і зветься "доступом". Вам потрібен доступ до оригінальних даних, а не до копій, з можливістю їх змінювати. Доступ з кількох місць = Rc (якщо з різних потоків - Arc). Можливість змінювати - RefCell.

Подякували: Teg Miles1

9

Re: Зміна значення self змінної у функції замикання

Нарешті розібрався. Rc — це reference counting, що дає кілька власників на незмінний тип,
а RefCell — це незмінний контейнер, що дає можливість змінювати значення того, що всередині нього.
Вийшло ось:

        full_dict_indexes: Rc<RefCell<HashMap<String, DictTuple>>>, //Оголошення в структурі

        let new_full_dict_indexes =
        Rc::new(RefCell::new(get_full_dict_indexes(&new_window))); //первинна ініціалізація

        
        let default_dict_paths_name = "current-dict-path".to_string();
        let full_dict_indexes = Rc::clone(&self.full_dict_indexes);
        let new_window = Rc::new(self.window.clone());
        let settings = self.settings.clone();
        self.settings.connect_changed(
            Some(&default_dict_paths_name), move |_, _| {
                 let default_dict_paths: String =
                    settings.string("current-dict-path").to_string();
                if default_dict_paths != ""{
                    reload_dict_indexes(Rc::clone(&full_dict_indexes), &new_window, &settings); //Використання для оновлення значення змінної
                    //println!("{}", full_dict_indexes.borrow().keys().count());
                }
            });

Тепер треба розібратися де доцільно поміняти .clone() на Rc::clone().
Бо перше створює повну копію і може з'їсти забагато ресурсів, але зберігає функціонал складних змінних.
А друге створює поверхневу копію, тому заощаджує ресурси, але може порушити функціонал складних змінних.

10

Re: Зміна значення self змінної у функції замикання

Дрібна порада: є така штука, як скорочений синтаксис ініціалізації структури. Якщо змінна зветься так само, як і елемент структури, і вона переміщується в структуру, то можна скоротити, замість

    let main_window = MainWindow{
        window: window,
        settings: settings,
    }

написати

    let main_window = MainWindow{
        window,
        settings,
    }

А для цього можна назвати ці змінні так само. Тобто не app_window і new_settings, а просто window і settings.

Подякували: leofun011

11

Re: Зміна значення self змінної у функції замикання

По клонуванню:

let full_dict_indexes = Rc::clone(&self.full_dict_indexes); //поза замиканням — вже є клон
 reload_dict_indexes(Rc::clone(&full_dict_indexes), //у замиканні — тут клонування вже не потрібне, бо full_dict_indexes буде переміщене сюди
Подякували: leofun011

12

Re: Зміна значення self змінної у функції замикання

koala написав:

По клонуванню:

let full_dict_indexes = Rc::clone(&self.full_dict_indexes); //поза замиканням — вже є клон
 reload_dict_indexes(Rc::clone(&full_dict_indexes), //у замиканні — тут клонування вже не потрібне, бо full_dict_indexes буде переміщене сюди

Там потрібно клонування, бо це вже параметр зовнішньої функції, куди винесено всі дії.
Без клонування буде ось така помилка:

error[E0507]: cannot move out of `full_dict_indexes`, a captured variable in an `Fn` closure
   --> src/main_window.rs:543:41
    |
535 |         let full_dict_indexes = Rc::clone(&self.full_dict_indexes);
    |             ----------------- captured outer variable
...
539 |             Some(&default_dict_paths_name), move |_, _| {
    |                                             ----------- captured by this `Fn` closure
...
543 |                     reload_dict_indexes(full_dict_indexes, &new_window, &settings);
    |                                         ^^^^^^^^^^^^^^^^^ move occurs because `full_dict_indexes` has type `Rc<RefCell<HashMap<std::string::String, (std::string::String, std::string::String, std::string::String, std::string::String, BTreeMap<std::string::String, (u32, u32)>)>>>`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.

13

Re: Зміна значення self змінної у функції замикання

Teg Miles написав:

Там потрібно клонування, бо це вже параметр зовнішньої функції, куди винесено всі дії.
Без клонування буде ось така помилка:

error[E0507]: cannot move out of `full_dict_indexes`, a captured variable in an `Fn` closure
   --> src/main_window.rs:543:41
    |
535 |         let full_dict_indexes = Rc::clone(&self.full_dict_indexes);
    |             ----------------- captured outer variable
...
539 |             Some(&default_dict_paths_name), move |_, _| {
    |                                             ----------- captured by this `Fn` closure
...
543 |                     reload_dict_indexes(full_dict_indexes, &new_window, &settings);
    |                                         ^^^^^^^^^^^^^^^^^ move occurs because `full_dict_indexes` has type `Rc<RefCell<HashMap<std::string::String, (std::string::String, std::string::String, std::string::String, std::string::String, BTreeMap<std::string::String, (u32, u32)>)>>>`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.

Ні, там клонуваня теж варто уникнути.

14

Re: Зміна значення self змінної у функції замикання

А, зрозумів. Це тому що функція не FnOnce, і повторний виклик уже не може бути виконаним, бо змінна була переміщена.
Але тоді краще передавати в reload_dict_indexes &Rc<>... чи ще краще - розгорнутий &mut ..., за сенсом. Вам же в reload_dict_indexes насправді Rc<RefCell> не потрібен, а потрібен &mut, правильно?

Подякували: leofun011

15

Re: Зміна значення self змінної у функції замикання

koala написав:

А, зрозумів. Це тому що функція не FnOnce, і повторний виклик уже не може бути виконаним, бо змінна була переміщена.
Але тоді краще передавати в reload_dict_indexes &Rc<>... чи ще краще - розгорнутий &mut ..., за сенсом. Вам же в reload_dict_indexes насправді Rc<RefCell> не потрібен, а потрібен &mut, правильно?

Я пробував спочатку зробити &mut, але не вийшло.
Переробив на &Rc<>, надто захопився клонуванням, де можна було звичайним посиланням передати:).

16

Re: Зміна значення self змінної у функції замикання

Нема нічого неправильного в передачі посилання на Rc, це економить одне клонування; але дизайн мови Rust дуже сильно підштовхує до чистих інтерфейсів. Функції потрібен об'єкт? То треба передати цей об'єкт (чи посилання), а не дві обгортки навколо нього. А розгортати треба там, звідки функція викликається.

Подякували: leofun011