我遇到了与这个问题中提到的相同问题。简而言之,他的问题是由于在闭包内使用而借用了一个可变的对象,并且由于在函数(或本例中为宏)内部使用而借用了不可变的对象。
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);
}
}
所以我脑子里有以下问题:
关于哪一种是正确的解决方案,我想说这取决于用例。它们非常相似,在大多数情况下并不重要,除非有其他因素影响决定。我不知道还有第三种解决方案。
但是,闭包不仅仅是匿名函数,而是也匿名结构:闭包是调用匿名函数的匿名结构。该结构的成员是对借用值的引用。这很重要,因为与函数不同,结构需要初始化并可能移动。这意味着闭包借用的值越多,初始化并作为参数传递给函数(按值)的成本就越高。同样,如果您在循环内初始化闭包,则初始化可能会在每次迭代时发生(如果未在循环外进行优化),从而使其性能低于在循环外初始化它。
我们可以尝试将第一个示例脱糖为以下代码:
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 上尝试了这一点,据我所知,最终的组装结果是相同的。 然而,优化并不能涵盖所有情况。因此,如果性能很重要,那么基准测试就是最佳选择。