Search code examples
rustgtklifetimeborrowinggtk-rs

Why does cloning data inside a closure not prevent the error "closure may outlive the current function"?


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

Solution

  • 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)