Почему клонирование данных внутри замыкания не предотвращает ошибку «замыкание может пережить текущую функцию»? - PullRequest
2 голосов
/ 03 июля 2019

Я создал приложение GTK с помощью gtk-rs.Когда я строю главное окно, я хочу использовать некоторые динамические параметры, такие как высота окна.Я создал структуру, которая содержит все такие настройки, и хочу использовать ее в качестве входного параметра для функции, создающей пользовательский интерфейс:

fn main() {
    let application =
        gtk::Application::new(Some("id"), Default::default())
            .expect("Initialization failed...");

    let config = Config {width: 100., height: 100.};
    application.connect_activate(|app| {
        build_ui(app, config.clone());
    });

    // Use config further

    application.run(&args().collect::<Vec<_>>());
}

#[derive(Debug, Clone)]
pub struct Config {
    pub width: f64,
    pub height: f64,
}

fn build_ui(application: &gtk::Application, config: Config) {
    ...
}

Я не могу использовать ссылку на config при вызове build_uiпоскольку эту функцию можно вызывать после завершения основной функции, и, следовательно, структура config больше не может существовать.

Моя идея состояла в том, чтобы создать копию структуры config (это всего лишь несколько примитивных переменных), которая существуеткроме первоначального, и поэтому я не столкнулся бы с проблемами жизни или владения.

Это правильный подход?Что я делаю неправильно?Я получаю ту же ошибку, которую получил при заимствовании структуры config:

error[E0373]: closure may outlive the current function, but it borrows `config`, which is owned by the current function
  --> src/main.rs:36:34
   |
36 |     application.connect_activate(|app| {
   |                                  ^^^^^ may outlive borrowed value `config`
37 |         build_ui(app, config.clone());
   |                       ------ `config` is borrowed here

1 Ответ

3 голосов
/ 03 июля 2019

Общее объяснение

Минимальное воспроизведение аналогичного выпуска:

fn move_and_print(s: String) {
    println!("{}", s);
}

fn main() {
    let s = String::from("Hello");

    let print_cloned_s = || println!("{}", s.clone());

    move_and_print(s);
    print_cloned_s();
}

Компилятор жалуется:

error[E0505]: cannot move out of `s` because it is borrowed

Я хочу клонировать s, чтобы избежать заимствования и, следовательно, получить возможность впоследствии его использовать. Итак, как компилятор может сказать, что s заимствовано?

Это прежнее рассуждение совершенно верно, однако, есть тонкость: подпись Clone::clone равна clone(&self) -> Self. Поэтому, когда вызывается clone, данные заимствуются функцией клона !

Решение состоит в том, чтобы клонировать данные до , создавая замыкание, а затем переместить их в замыкание:

fn move_and_print(s: String) {
    println!("{}", s);
}

fn main() {
    let s = String::from("Hello");

    // I clone `s` BEFORE creating the closure:
    let cloned_s = s.clone();

    // Then I move the cloned data into the closure:
    let print_cloned_s = move || println!("{}", cloned_s);

    move_and_print(s);
    print_cloned_s();
}

Решение вашей фактической ошибки

Как я уже сказал, вы должны клонировать конфигурацию и переместить этот клон в замыкание:

let cloned_config = config.clone();

application.connect_activate(move |app| {
    build_ui(app, cloned_config.clone());
});

Вы также должны добавить второй вызов клона, чтобы позволить закрытию быть Fn, а не FnOnce. Действительно, если вы переместите вашу конфигурацию внутрь build_ui, эту функцию нельзя использовать дважды. См. этот вопрос для получения дополнительной информации.


Если я хорошо понимаю ваши потребности, config предназначена для конфигурации только для чтения, которая должна использоваться совместно. В этой ситуации я бы вообще не перемещал его, например, изменив подпись build_ui на:

fn build_ui(application: &gtk::Application, config: &Config)
...