Почему клонирование данных внутри замыкания не предотвращает ошибку "замыкание может пережить текущую функцию"?
Я создал приложение 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: >k::Application, config: Config) {
...
}
Я не могу использовать ссылку на config
при звонке build_ui
поскольку эта функция может быть вызвана после того, как основная функция завершена, и, таким образом, структура конфигурации больше не может существовать.
Моя идея состояла в том, чтобы создать копию структуры 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
2 ответа
Общее объяснение
Минимальное воспроизведение аналогичного выпуска:
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: >k::Application, config: &Config)
Ненавижу говорить, что санкционированный ответ не очень точен. Это правильно, но имеет небольшую разницу с кодом OP. На самом деле, если внимательно прочитать оригинальный код, нет оснований полагать, что rustc
не может заключить локальную переменную config
пережить connect_activate
вызов функции. Он отвергает это по другим причинам.
Более точный минимальный воспроизводимый пример:
fn reference_and_print(s: &str) {
println!("{}", s);
}
fn closure_and_print<F: Fn()>(f: F) {
f();
}
fn main() {
let s = "Hello";
reference_and_print(s);
closure_and_print(|| {
println!("{}", s);
});
reference_and_print(s);
}
Это компилируется. Но если изменить только одну строку:
fn closure_and_print<F: Fn() + 'static>(f: F) {
f();
}
Это тогда вызывает may outlive borrowed value
ошибка. Довольно удивительно.
На самом деле, изучив gtk-rs
код, я заметил, что замыкания с 'static
связаны везде. Ничто не переживает 'static
если вы не владеете ими. Вот почему нужно использовать move
замыкания для владения захваченными переменными при использовании gtk-rs
:
let cloned = config.clone();
application.connect_activate(move |app| {
build_ui(app, cloned);
});