我正在阅读Rust书的生命周期章节,我看到了一个命名/显式生命周期的例子:

struct Foo<'a> {
    x: &'a i32,
}

fn main() {
    let x;                    // -+ x goes into scope
                              //  |
    {                         //  |
        let y = &5;           // ---+ y goes into scope
        let f = Foo { x: y }; // ---+ f goes into scope
        x = &f.x;             //  | | error here
    }                         // ---+ f and y go out of scope
                              //  |
    println!("{}", x);        //  |
}                             // -+ x goes out of scope

我很清楚,编译器阻止的错误是分配给x的引用的use-after-free:内部作用域完成后,f和&f。X变得无效,并且不应该分配给X。

我的问题是,这个问题可以很容易地分析掉,而不使用显式的` a lifetime,例如,通过推断一个更广泛的范围的引用的非法赋值(x = &f.x;)。

在哪些情况下,实际上需要显式生命期来防止free后使用(或其他类?)错误?


当前回答

If a function receives two references as arguments and returns a reference, then the implementation of the function might sometimes return the first reference and sometimes the second one. It is impossible to predict which reference will be returned for a given call. In this case, it is impossible to infer a lifetime for the returned reference, since each argument reference may refer to a different variable binding with a different lifetime. Explicit lifetimes help to avoid or clarify such a situation.

同样地,如果一个结构体包含两个引用(作为两个成员字段),那么该结构体的成员函数可能有时返回第一个引用,有时返回第二个引用。同样,显式的生存期可以防止这种歧义。

在一些简单的情况下,存在生命期省略,编译器可以推断生命期。

其他回答

书中的箱子设计非常简单。生命的话题被认为是复杂的。

编译器无法轻易推断具有多个参数的函数的生存期。

另外,我自己的可选板条箱有一个OptionBool类型的as_slice方法,其签名实际上是:

fn as_slice(&self) -> &'static [bool] { ... }

编译器绝对不可能发现这个。

让我们来看看下面的例子。

fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'a u32 {
    x
}

fn main() {
    let x = 12;
    let z: &u32 = {
        let y = 42;
        foo(&x, &y)
    };
}

在这里,显式生命期很重要。这是因为foo的结果与它的第一个参数('a)具有相同的生命期,所以它可能比它的第二个参数更长寿。这是由foo签名中的生存期名称表示的。如果你在foo调用中切换参数,编译器会抱怨y的生存时间不够长:

error[E0597]: `y` does not live long enough
  --> src/main.rs:10:5
   |
9  |         foo(&y, &x)
   |              - borrow occurs here
10 |     };
   |     ^ `y` dropped here while still borrowed
11 | }
   | - borrowed value needs to live until here

你的例子不能工作的原因很简单,因为Rust只有局部生存期和类型推断。你的建议需要全局推理。当您有一个生命周期不能被省略的引用时,必须对其进行注释。

我认为生命周期注释是关于给定ref的契约,它只在接收范围内有效,而在源范围内仍然有效。在同一个生命周期中声明更多的引用会合并作用域,这意味着所有的源引用都必须满足这个契约。 这样的注释允许编译器检查契约的实现。

作为Rust的新手,我的理解是显式生命期有两个目的。

Putting an explicit lifetime annotation on a function restricts the type of code that may appear inside that function. Explicit lifetimes allow the compiler to ensure that your program is doing what you intended. If you (the compiler) want(s) to check if a piece of code is valid, you (the compiler) will not have to iteratively look inside every function called. It suffices to have a look at the annotations of functions that are directly called by that piece of code. This makes your program much easier to reason about for you (the compiler), and makes compile times managable.

第一点。,考虑以下用Python编写的程序:

import pandas as pd
import numpy as np

def second_row(ar):
    return ar[0]

def work(second):
    df = pd.DataFrame(data=second)
    df.loc[0, 0] = 1

def main():
    # .. load data ..
    ar = np.array([[0, 0], [0, 0]])

    # .. do some work on second row ..
    second = second_row(ar)
    work(second)

    # .. much later ..
    print(repr(ar))

if __name__=="__main__":
    main()

打印出来

array([[1, 0],
       [0, 0]])

这种行为总是让我吃惊。实际上,df与ar共享内存,所以当df的某些内容在work中发生变化时,这些变化也会影响ar。然而,在某些情况下,出于内存效率的原因(无复制),这可能正是您想要的。这段代码中真正的问题是,函数second_row返回第一行而不是第二行;祝你调试好运。

考虑一个用Rust编写的类似程序:

#[derive(Debug)]
struct Array<'a, 'b>(&'a mut [i32], &'b mut [i32]);

impl<'a, 'b> Array<'a, 'b> {
    fn second_row(&mut self) -> &mut &'b mut [i32] {
        &mut self.0
    }
}

fn work(second: &mut [i32]) {
    second[0] = 1;
}

fn main() {
    // .. load data ..
    let ar1 = &mut [0, 0][..];
    let ar2 = &mut [0, 0][..];
    let mut ar = Array(ar1, ar2);

    // .. do some work on second row ..
    {
        let second = ar.second_row();
        work(second);
    }

    // .. much later ..
    println!("{:?}", ar);
}

编译这个,你得到

error[E0308]: mismatched types
 --> src/main.rs:6:13
  |
6 |             &mut self.0
  |             ^^^^^^^^^^^ lifetime mismatch
  |
  = note: expected type `&mut &'b mut [i32]`
             found type `&mut &'a mut [i32]`
note: the lifetime 'b as defined on the impl at 4:5...
 --> src/main.rs:4:5
  |
4 |     impl<'a, 'b> Array<'a, 'b> {
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...does not necessarily outlive the lifetime 'a as defined on the impl at 4:5
 --> src/main.rs:4:5
  |
4 |     impl<'a, 'b> Array<'a, 'b> {
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^

事实上你得到了两个错误,还有一个是'a '和'b的角色互换了。查看second_row的注释,我们发现输出应该是&mut &'b mut [i32],也就是说,输出应该是一个引用,引用的生命期为'b (Array的第二行生命期)。但是,因为我们返回的是第一行(它的生存期为'a),编译器会报错生存期不匹配。在正确的地方。在正确的时间。调试是轻而易举的事。