【问题标题】:Why does cloning data inside a closure not prevent the error "closure may outlive the current function"?为什么在闭包中克隆数据不能防止“闭包可能比当前函数寿命更长”的错误?
【发布时间】:2019-11-13 22:22:37
【问题描述】:

我用 gtk-rs 构建了一个 GTK 应用程序。当我构建主窗口时,我想使用一些动态参数,例如窗口高度。我创建了一个包含所有此类设置的结构,并希望将其用作构建 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) {
    ...
}

在调用build_ui 时,我不能使用对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

【问题讨论】:

    标签: rust gtk lifetime borrowing gtk-rs


    【解决方案1】:

    讨厌说批准的答案不是很准确。这是正确的,但与 OP 代码有细微的差别。实际上,如果仔细阅读原始代码,没有理由相信rustc 不能推断出局部变量configconnect_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 更长寿。这就是为什么在使用gtk-rs 时必须使用move 闭包来拥有捕获的变量:

    let cloned = config.clone();
    application.connect_activate(move |app| {
        build_ui(app, cloned);
    });
    

    【讨论】:

      【解决方案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(&amp;self) -&gt; Self。所以当clone被调用时,数据被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 里面,这个函数就不能被使用两次。请参阅this question 了解更多信息。


      如果我理解您的需求,config 旨在成为必须共享的只读配置。在这种情况下,我根本不会移动它,例如将build_ui 的签名更改为:

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

      【讨论】:

      • 我看到你的评论并试过了,没有用。我不确定我是否理解正确,但尝试您的示例会返回相同的错误:cannot move out of captured variable in an 'Fn' closure
      • @Jonas 这不是相同的错误,而是不同的错误。
      • 是的,这是另一个错误。如果没有更多信息,我们无法为您提供帮助。
      • 对不起,我的意思与我从最初的评论中尝试您的方法时遇到的错误相同。
      猜你喜欢
      • 1970-01-01
      • 2020-07-15
      • 2019-07-02
      • 2022-01-14
      • 1970-01-01
      • 2018-07-17
      • 2017-10-18
      • 2015-03-09
      相关资源
      最近更新 更多