为什么函数参数生命周期与函数内部绑定的生命周期不同?

Why is function argument lifetime different to the lifetime of a binding inside a function?

再试一次 VisualRust 看看他们能走多远,我写了几行代码。和往常一样,代码让我在 Whosebug 上写了一个问题...

先看,再看我的问题:

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() 中的块,它们仍然具有相同的生命周期。有了方块,counter 会在 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的生命周期基本上应该会导致相同的错误信息,对吧?如果代码捕获闭包的状态,那么传入指针或者变量是否绑定在函数内部应该无关紧要,对吧?但显然,这两种情况有神奇的不同。

谁能解释一下,为什么它们不同(错误、功能、深刻的见解,...?),如果有一个简单的规则可以采纳,以防止时不时地在这些问题上浪费时间?

区别不在于使用局部变量还是使用参数。参数是完全普通的本地人。事实上,这个版本的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。它没有自己的状态,它在其他地方使用一个整数作为它的 scratch space。因此它需要担心该位置的生命周期。函数签名需要传达闭包只有在它仍然可以使用传入的引用时才能工作。这可以用生命周期参数来表达:

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。可变 reference 仅在需要与其他代码共享突变结果并且您不能将新值传递给该代码时才有用。

当然,有时需要以复杂的方式处理引用。不幸的是,似乎没有一种快速简便的方法来学会自信地处理这些问题。这是一个很大的教学挑战,但到目前为止,似乎每个人都只是挣扎了一段时间,然后随着经验的增加,问题逐渐减少了。不,没有一个简单的规则可以解决所有一生的烦恼。

1 在所有情况下,return 类型必须是 FnMut。你只是还没有收到关于那个的错误,因为你当前的错误发生在编译的早期阶段。