I built a GTK application with gtk-rs. When I build the main window, I want to use some dynamic parameters such as window height. I created a struct which contains all such settings and want to use this as an input parameter for the function building the UI:
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) {
...
}
I can't use a reference to config
when calling build_ui
since this function could be called after the main function finished and thus the config struct could not exist anymore.
My idea was to create a copy of the config struct (it is only a few primitive variables), which exists apart of the original one and thus I wouldn't run into lifetime or ownership issues.
Is this the right approach? What am I doing wrong? I get the same error I got from borrowing the config struct:
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
General explanation
Minimal reproduction of a similar issue:
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();
}
The compiler complains:
error[E0505]: cannot move out of `s` because it is borrowed
I want to clone s
to avoid a borrow, and thus to be allowed to consume it afterwards. So, how can the compiler say that s
is borrowed?
This former reasoning is totally correct, however, there is a subtlety: the signature of Clone::clone
is clone(&self) -> Self
. So when clone
is called, the data is borrowed by the clone function!
The solution is to clone the data before creating the closure, and then to move it into the closure:
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();
}
Solving your actual error
As I said, you must clone the configuration and move this clone inside the closure:
let cloned_config = config.clone();
application.connect_activate(move |app| {
build_ui(app, cloned_config.clone());
});
You must also add a second clone call to allow the closure to be a Fn
and not a FnOnce
. Indeed, if you move your config inside build_ui
, the function cannot be used twice. See this question for more information.
If I understand well your need, config
is intended to be a read-only configuration that must be shared. In this situation, I would not move it at all, for example by changing the signature of build_ui
to:
fn build_ui(application: >k::Application, config: &Config)
Hate to say that the sanctioned answer isn't very accurate. It is correct but has subtle difference to the OP code. Actually if reading the original code carefully, there is no reason to believe that rustc
can't conclude the local variable config
outlive the connect_activate
function call. It rejects it for other reasons.
A more accurate minimal reproducible example is:
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);
}
This compiles. But, if only change one line:
fn closure_and_print<F: Fn() + 'static>(f: F) {
f();
}
This then causes may outlive borrowed value
error. Pretty surprising.
In fact, by examining gtk-rs
code, I notice that closures with 'static
bound are everywhere. Nothing outlives 'static
unless you own them. That's why one must use move
closures to own the captured variables when using gtk-rs
:
let cloned = config.clone();
application.connect_activate(move |app| {
build_ui(app, cloned);
});