【问题标题】:Why is function argument lifetime different to the lifetime of a binding inside a function?为什么函数参数的生命周期与函数内绑定的生命周期不同?
【发布时间】:2015-12-08 19:11:09
【问题描述】:

再次尝试 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() 中的块,它们仍然具有相同的生命周期。使用该块,counter 将在cnt 之前死亡。然而,Rust 抱怨道:

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

如果你现在看alt_make_counter() 函数,state 的生命周期基本上应该会导致相同的错误信息,对吧?如果代码捕获了闭包的状态,那么是否传入指针或变量是否绑定在函数内部都无关紧要,对吧?但很明显,这两种情况是完全不同的。

谁能解释它们为什么不同(错误、功能、深刻见解……?),以及是否有一个可以采用的简单规则来防止不时在此类问题上浪费时间?

【问题讨论】:

    标签: closures rust


    【解决方案1】:

    区别不在于使用局部变量与使用参数。参数是完全普通的局部变量。事实上,这个版本的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 中的闭包关闭了&amp;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() -&gt; u32(尽管语法不同,因为它是一个特征)。

    避免此类问题的最简单规则是在引起问题时不要使用引用。这个闭包没有充分的理由借用它的状态,所以不要这样做。我不知道你是否属于这种情况,但我见过很多人认为&amp;mut 是改变某些东西的主要或唯一方法。 这是错误的。您可以只按值存储它,然后直接通过将它或包含它的更大结构存储在标记为mut 的局部变量中来改变它。可变引用仅在需要与其他代码共享突变结果并且您不能只将新值传递给该代码时才有用。

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

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

    【讨论】:

    • 我认为 Rust 中明确的太多只会给用户带来负担,而它实际上可能发生在“幕后”。如果涉及到通过引用传递,指针的概念可能是任何开始使用 Rust 的人的工作理论。因此,代码中的闭包捕获将是:对 u32 的引用。很难看出为什么这应该是一个问题。大多数人期望的是编译器会检查编写的代码,而不是make_counter() 的可能应用空间。只有当有人违反生命周期时,编译器才应该抱怨,恕我直言。
    • @BitTickler 这是一个巨大的争论,但这里有几点:(1)一旦你知道如何解决它,99% 的时间安抚借阅检查器几乎没有认知开销。 (2) 在函数签名中预先声明生命周期关系是有价值的文档(当然,如果你知道如何阅读它)和 (3) 不考虑它并让它被推断会导致更令人困惑的错误消息调用者和函数不同意生命周期。它还允许人们编写“错误”的函数并且在他们在棘手的情况下调用它们之前不会出错。
    • 显然,这场辩论没有对错之分。但这不是很有趣吗,类型推断和省略给出类型现在很流行,但是“生命周期推断”与“显式生命周期规范”仍然是一个悬而未决的问题?
    • @BitTickler 类型推断通常很好,但是在整个程序类型推断(Haskell,ML 变体)方面经验丰富的社区的共识是,默认情况下最好对顶级定义进行类型注释。 Rust 中的情况类似,在每个函数的生命周期内大多是推断出来的,但没有跨函数推断。有生命周期省略,它在一定程度上服务于相同的目的,但更简单、易于预测和本地化(仅取决于签名的其余部分,而不取决于其他函数)。
    • @BitTickler this question中提到了其中的一些内容
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-25
    • 1970-01-01
    • 2020-11-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多