for 循环内的 Rust 闭包定义

问题描述 投票:0回答:1

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

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 参考文献中找到这个问题的明确答案。救命!
rust closures borrow-checker
1个回答
1
投票

关于哪一种是正确的解决方案,我想说这取决于用例。它们非常相似,在大多数情况下并不重要,除非有其他因素影响决定。我不知道还有第三种解决方案。

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

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

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 上尝试了这一点,据我所知,最终的组装结果是相同的。 然而,优化并不能涵盖所有情况。因此,如果性能很重要,那么基准测试就是最佳选择。

© www.soinside.com 2019 - 2024. All rights reserved.