首页 > 解决方案 > rust 闭包定义 insdie 一个 for 循环

问题描述

我遇到了与此问题中提到的相同的问题。简而言之,他的问题是借用一个可变对象,因为它在闭包内使用,并且由于在函数(或本例中的宏)内部使用而将其借用为不可变。

fn main() {
    let mut count = 0;

    let mut inc = || {
        count += 2;
    };

    for _index in 1..5 {
        inc();
        println!("{}", count);
    }
}

这个问题的一种解决方案是在 for 循环内而不是在 for 循环外定义闭包,或者通过使用闭包的参数传递可变引用来避免捕获变量:

1.

  fn main() {
      let mut count = 0;

      for _index in 1..5 {
          let mut inc = || {
              count += 2;
          };
          inc();
          println!("{}", count);
      }
  }
  fn main() {
      let mut count = 0;
    
      let inc = | count: &mut i32| {
          *count += 2;
      }; 
    
      for _index in 1..5 {
          inc(&mut count);
          println!("{}", count);
      }
  }

所以我有以下几个问题:

  1. 其中哪一项遵循最佳实践解决方案?
  2. 是否有第三种正确的做事方式?
  3. 根据我的理解,闭包只是匿名函数,因此多次定义它们与一次定义它们一样有效。但我无法在官方的 rust 参考资料中找到这个问题的明确答案。帮助!

标签: rustclosuresborrow-checker

解决方案


关于哪一个是正确的解决方案,我会说这取决于用例。它们是如此相似,在大多数情况下都无关紧要,除非有其他东西可以影响决定。我不知道任何第三种解决方案。

但是,闭包不仅匿名函数,而且匿名结构:闭包是调用匿名函数的匿名结构。结构的成员是对借用值的引用。这很重要,因为与函数不同,结构需要初始化并可能移动。这意味着您的闭包借用的值越多,初始化并作为参数传递给函数(按值)的成本就越高。同样,如果您在循环内初始化闭包,则每次迭代都可能发生初始化(如果未在循环外进行优化),使其性能不如在循环外初始化。

我们可以尝试将第一个示例脱糖为以下代码:

struct IncClusureStruct<'a> {
    count: &'a mut i32,
}
fn inc_closure_fn<'a>(borrows: &mut IncClusureStruct<'a>) {
    *borrows.count += 2
}

fn main() {
    let mut count = 0;

    for _index in 1..5 {
        let mut inc_struct = IncClusureStruct { count: &mut count };
        inc_closure_fn(&mut inc_struct);
        println!("{}", count);
    }
}

注意:编译器不一定完全这样做,但它是一个有用的近似值。

在这里您可以看到闭包结构IncClusureStruct及其函数inc_closure_fn,它们一起用于提供inc. 你可以看到我们在循环中初始化了这个结构,然后立即调用它。如果我们要对第二个示例进行脱糖,IncClusureStruct则将没有成员,但inc_closure_fn会采用引用计数器的附加参数。然后计数器引用将转到函数调用而不是结构初始化程序。

这两个示例最终在效率方面是相同的,因为在两种情况下传递给函数的实际值的数量是相同的:1 个引用。用一个成员初始化结构与简单地初始化成员本身相同,当您到达机器代码时,包装结构就消失了。我在 Godbolt 上试过这个,据我所知,生成的程序集是一样的。但是,优化并不能涵盖所有情况。因此,如果性能很重要,那么基准测试就是要走的路。


推荐阅读