Last Regrets

2023-3-24

· sdttttt

好长时间没写博客了,现在在上海开始上班,算是新的开始。

今天随便读了一下Rust死灵书,记录一下自己的领悟。

let mut data = vec![1, 2, 3];
let x = &data[0];
data.push(4);
println!("{}", x);
'a: {
    let mut data: Vec<i32> = vec![1, 2, 3];
    'b: {
        // 'b 这个生命周期范围如我们所愿地小(刚好够 println!)
        let x: &'b i32 = Index::index::<'b>(&'b data, 0);
        'c: {
            // 这里有一个临时作用域,我们不需要更长时间的 &mut 借用
            Vec::push(&'c mut data, 4);
        }
        println!("{}", x);
    }
}

这里的问题更微妙、更有趣。我们希望 Rust 拒绝这个程序,理由如下:我们有一个存活的共享引用xdata的一个子集,当我们试图把data的可变引用传给push时。这将创建一个可变引用的别名,而这将违反引用的第二条规则。

然而,这根本不是 Rust 认为这个程序有问题的原因。Rust 不理解x是对data的一个子集的引用。它根本就不理解Vec。它看到的是,x必须在'b范围内保持存活才能被打印;接下来,Index::index的签名要求我们对data的引用必须在'b范围内存活。当我们试图调用push时,它看到我们试图构造一个&'c mut data。Rust 知道'c包含在'b中,并拒绝了我们的程序,因为&'b data必然还存活着!

在这里我们看到,和我们真正想要保证的引用规则语义相比,生命周期系统要粗略得多。在大多数情况下,这完全没问题,因为它使我们不用花整天的时间向编译器解释我们的程序。然而,这确实意味着有部分程序对于 Rust 的真正的语义来说是完全正确的,但却被拒绝了,因为 lifetime 太傻了。

上面这一段是生命周期的一个例子,我愣是看了好长时间才懂了下面说的是什么。

简单来说上面的代码违反了Rust的一个引用规则:不可变引用可以存在多个,但是可变引用只能存在一个。

x在b区域内被创建,为一个不可变引用,但是data.push创造了一个可变引用,所以这里无法通过编译。

解决方法也很简单,把println!("{}", x);往上面调整一行就可以了,这样x不再使用,rust就认为x的生命周期已经结束。