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

Bit*_*ler 2 closures rust

给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();
}
Run Code Online (Sandbox Code Playgroud)

make_counter()和之间的区别在于alt_make_counter(),在一种情况下,状态是指向传递给函数的可变u32的指针,而在另一种情况下,它是在函数内定义的可变u32.由于test_make_counter()函数清楚地显示,因此闭包的寿命比变量长cnt.即使我移除了内部块,test_make_counter()它们仍然具有相同的寿命.随着块,counter将死亡之前cnt.然而,Rust抱怨道:

src\main.rs(4,2):错误:捕获的变量state不会超过封闭的闭包src\main.rs(3,1):警告:注意:捕获的变量对块上定义的匿名生命#1有效在3:0

如果你alt_make_counter()现在看一下这个函数,它的生命周期state应该基本上会产生相同的错误信息吧?如果代码捕获了闭包的状态,那么指针是否被传入或者变量是否绑定在函数内部无关紧要,对吧?但很明显,这2个案例神奇地不同.

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

小智 5

区别在于使用局部变量与使用参数.参数完全是普通的本地人.实际上,这个版本的alt_make_counter作品1:

fn alt_make_counter (mut state: u32) -> Box<FnMut() -> u32> {
    Box::new(move || {let ret = state; state = state + 1; ret })
}
Run Code Online (Sandbox Code Playgroud)

问题是关闭在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 })
}
Run Code Online (Sandbox Code Playgroud)

请注意,'a它也附加到FnMut() -> u32(虽然使用不同的语法,因为它是一个特征).

避免此类问题的最简单规则是在引发问题时不使用引用.这种关闭没有充分理由借用它的状态,所以不要这样做.我不知道你是否属于这个,但我看到一群人的印象&mut是改变某些东西的主要或唯一方式.那是错的.您可以按值存储它,然后直接通过将其存储或者包含它的较大结构直接变换到标记为的局部变量中mut.只有当突变的结果需要与其他代码共享并且您不能将新值传递给该代码时,可变引用才有用.

当然,有时需要以复杂的方式处理参考文献.不幸的是,似乎没有一种快速简便的方法可以学会自信地处理这些问题.这是一个很大的教育挑战,但到目前为止,似乎每个人都只是挣扎了一段时间,然后随着他们变得更有经验,逐渐减少了问题.不,没有一个简单的规则可以解决所有生命中的困境.

1返回类型必须FnMut在所有情况下.您刚才没有收到错误,因为您当前的错误发生在编译的早期阶段.

  • @BitTickler类型推断总体上是好的,但社会的共识,经历了整个程序类型推断(哈斯克尔,ML变种)是它更好地键入默认注释顶级定义.Rust中的情况类似,在每个函数中,生命周期大多是推断的,但没有跨函数推断.有life*elision*,它在一定程度上起到相同的作用,但更简单,更容易预测,并且是本地的(仅取决于签名的其余部分,而不取决于其他功能). (5认同)
  • @BitTickler这是一个巨大的争论,但这里有几点:(1)一旦你知道你的方式周围,时间安抚借检查99%有很少的认知开销.(2)宣布了在函数签名前寿命的关系是有价值的文件(如果你知道如何读它,当然)和(3)不考虑它,让它可以推断将导致更加混乱的错误消息时呼叫者和职能部门对一生都持不同意见.它还允许人们编写"错误"的功能,直到他们在棘手的情况下调用它们才会出错. (4认同)