Why does cloning data inside a closure not prevent

2020-04-10 01:33发布

问题:

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: &gtk::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

回答1:

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: &gtk::Application, config: &Config)


回答2:

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);
});