再次尝试 VisualRust,看看他们能走多远,我写了几行代码。和往常一样,代码让我在 stackoverflow 上写了一个问题......
先看,稍后再读我的问题:
fn make_counter( state : &mut u32 ) -> Box<Fn()->u32>
{
Box::new(move || {let ret = *state; *state = *state + 1; ret })
}
fn test_make_counter() {
let mut cnt : u32 = 0;
{
let counter = make_counter( & mut cnt );
let x1 = counter();
let x2 = counter();
println!("x1 = {} x2 = {}",x1,x2);
}
}
fn alt_make_counter ( init : u32 ) -> Box<Fn()->u32> {
let mut state = init;
Box::new(move || {let ret = state; state = state + 1; ret })
}
fn test_alt_make_counter() {
let counter = alt_make_counter( 0u32 );
let x1 = counter();
let x2 = counter();
println!("x1 = {} x2 = {}",x1,x2);
}
fn main() {
test_make_counter();
test_alt_make_counter();
}
make_counter() 和 alt_make_counter() 之间的区别是,在一种情况下,状态是一个指向传递给函数的可变 u32 的指针,并且在其他情况下,它是函数内部定义的可变 u32。正如 test_make_counter() 函数清楚地表明的那样,闭包的生命周期不可能比变量 cnt
的生命周期长。即使我删除了 test_make_counter() 内的 block ,它们仍然具有相同的生命周期。通过该 block ,计数器
将在cnt
之前消亡。然而,Rust 提示道:
src\main.rs(4,2): error : captured variable
state
does not outlive the enclosing closure src\main.rs(3,1): warning : note: captured variable is valid for the anonymous lifetime #1 defined on the block at 3:0
如果您现在查看 alt_make_counter()
函数,state
的生命周期基本上应该会导致相同的错误消息,对吧?如果代码捕获了闭包的状态,那么是否传入指针或变量是否绑定(bind)在函数内部应该无关紧要,对吧?但显然,这两种情况有着神奇的不同。
谁可以解释一下,为什么它们是不同的(错误、功能、深刻的见解,...?),以及是否有一个简单的规则可以采用,以防止时不时地在此类问题上浪费时间?
最佳答案
区别不在于使用局部变量与使用参数。参数是完全普通的本地参数。事实上,这个版本的 alt_make_counter
可以工作1:
fn alt_make_counter (mut state: u32) -> Box<FnMut() -> u32> {
Box::new(move || {let ret = state; state = state + 1; ret })
}
问题是 make_counter
中的闭包通过 &mut u32
而不是 u32
关闭。它没有自己的状态,它使用其他地方的整数作为其临时空间。因此它需要担心该位置的生命周期。函数签名需要传达闭包只有在仍然可以使用传入的引用时才能工作。这可以用生命周期参数来表达:
fn make_counter<'a>(state: &'a mut u32) -> Box<FnMut() -> u32 + 'a> {
Box::new(move || {let ret = *state; *state = *state + 1; ret })
}
请注意,'a
也附加到 FnMut() -> u32
(尽管使用不同的语法,因为它是一个特征)。
避免此类麻烦的最简单规则是当引用引起问题时不使用。这个闭包没有充分的理由借用它的状态,所以不要这样做。我不知道你是否属于这种情况,但我见过很多人认为 &mut
是改变某些东西的主要或唯一方法。 这是错误的。您可以只按值存储它,然后通过将它或包含它的较大结构存储在标记为 mut 的局部变量中来直接改变它
。仅当突变结果需要与其他代码共享并且您不能仅将新值传递给该代码时,可变引用才有用。
当然,有时以复杂的方式处理引用是必要的。不幸的是,似乎没有一种快速、简单的方法来学会自信地处理这些问题。这是一个巨大的教学挑战,但到目前为止,似乎每个人都只是挣扎了一段时间,然后随着经验的增加,问题逐渐减少。不,没有任何一个简单的规则可以解决一生中的所有问题。
1 在所有情况下,返回类型都必须为 FnMut
。您只是还没有收到相关错误,因为您当前的错误发生在编译的早期阶段。
关于closures - 为什么函数参数的生存期与函数内绑定(bind)的生存期不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32536757/