参考在嵌套结构中的活动时间不够长

时间:2018-01-08 21:07:02

标签: rust

我正在创建一系列包含对较低级别结构的可变引用的数据结构。我一直非常乐意与ABC合作,但我尝试添加新图层DABCD实际上是用于协议解码的状态机的状态,但我已经删除了所有这些:

struct A {}

fn init_A() -> A {
    A {}
}

struct B<'l> {
    ed: &'l mut A,
}

fn init_B(mut e: &mut A) -> B {
    B { ed: e }
}

struct C<'l> {
    pd: &'l mut B<'l>,
}

fn init_C<'l>(mut p: &'l mut B<'l>) -> C<'l> {
    C { pd: p }
}

struct D<'lifetime> {
    sd: &'lifetime mut C<'lifetime>,
}

fn init_D<'l>(mut p: &'l mut C<'l>) -> D<'l> {
    D { sd: p }
}

fn main() {
    let mut a = init_A();
    let mut b = init_B(&mut a);
    let mut c = init_C(&mut b);

    // COMMENT OUT THE BELOW LINE FOR SUCCESSFUL COMPILE
    let mut d = init_D(&mut c);
}

我收到错误:

error[E0597]: `c` does not live long enough
  --> src/main.rs:38:1
   |
37 |     let mut d = init_D(&mut c);
   |                             - borrow occurs here
38 | }
   | ^ `c` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

我完全不了解DC相比,生命周期的变化情况有所不同:我不明白终身不匹配是什么。

2 个答案:

答案 0 :(得分:3)

我将解决有关代码无效的原因。

TL; DR:类型C<'l>D<'l>的生命周期的不变性以及对它们使用单​​个生命周期参数('l)导致这些类型的变量保持借用只要变量b存在,但变量c(由d借用)在b之前被删除。

借用检查器本质上是一个约束求解器。它搜索满足各种约束的最短生命期 0 :引用的寿命不得超过它引用的值,生命期必须服从函数签名和类型中指定的约束,并且生命期必须服从方差规则 1

0 - 参考的最短生命周期是最佳的,因为参考不会借用超过必要的值。

1 - Rust有一个variance的概念,它确定是否可以在期望值较小的生命周期的地方使用寿命较长的值。 Rustonomicon链接详细解释了它。

下面的代码是相关代码的简化版本,它失败并出现相同的错误:c的活动时间不够长。这些块标有变量的生命周期。 'a是变量a的生命周期,依此类推。这些生命周期由代码的结构决定,并且它们是固定的。

类型注释(B(&'ar A) -> B<'ar>等)的生命周期是变量。借用检查器会尝试为这些变量查找固定生命周期('a'b'c,“d)的有效分配。

let语句下方的评论显示了我将在下面解释的生命周期限制。

struct A;

struct B<'l>(&'l mut A);

struct C<'l>(&'l mut B<'l>);

struct D<'l>(&'l mut C<'l>);

fn main() {
    // lifetime 'a
    let mut a = A;
    { // lifetime 'b
        // B(&'r mut A) -> B<'ar>   
        let mut b = B(&mut a); 
        // 'r >= 'ar & 'r <= 'a
        { // lifetime 'c
            // C(&'br mut B<'ar>) -> C<'abr>  
            let mut c = C(&mut b); 
            // 'br <= 'b & 'abr = 'ar & 'br >= 'abr
            { // lifetime 'd
                // D(&'cr mut C<'abr>) -> D<'cabr> 
                let d = D(&mut c); 
                // 'cr <= 'c & 'cabr = 'abr & 'cr >= 'cabr
            }
        }
    }
}

首次转让

// B(&'r mut A) -> B<'ar>   
let mut b = B(&mut a); 
// 'r <= 'a & 'r >= 'ar

a的引用不能超过a,因此'r <= 'a

&'r mut A是'r的变体,因此我们可以将其传递给B<'ar>的类型构造函数,该构造函数需要&'ar mut A iff 'r >= 'ar

第二次作业

 // C(&'br mut B<'ar>) -> C<'abr>  
 let mut c = C(&mut b); 
 // 'br <= 'b & 'abr = 'ar & 'br >= 'abr

参考不能超过b'br <= 'b),&mut BB'abr = 'ar)不变,&'br mut B'br的变体1}}('br >= 'abr

d的分配类似于c

Rust似乎并没有考虑它作为可能的任务尚未遇到的生命周期。因此,'ar的可能分配为'a'b'abr的分配为'a'b'c等等。

这组约束归结为'ar = 'abr = 'cabr'ar允许的最小分配为'b。因此,bcd的类型为B<'b>C<'b>D<'b>。也就是说,变量d在生命周期c中保留对'b的引用,但在c生命周期结束时删除'c

如果我们移除d,那么c仍然会将b借用到生命周期结束'b,但这不是问题,因为{{1} }不会超过生命周期b

此说明仍然简化。例如,虽然'b的类型为cC<'b>并未在整个生命周期c中借用b,但它借用了它的一部分'b在定义'b之后开始,但我还没有明确的理解。

答案 1 :(得分:2)

原始代码中的init_*()函数总是返回一个类型,其生命周期参数等于您传入的引用的生命周期。由于您以这种方式构建引用链,因此您的所有生命周期都将结束同样,abcd的类型最终会成为AB<'a>C<'a>D<'a>。这可以在c之前完成,因为生命周期'a可以是b的范围,它可以满足所有约束条件。

但是,一旦将d添加到混合中,就没有单一的生命周期'a可以使所有引用都有效。生命周期'a不再是b的范围,因为c的生存时间不够长。它也不是c的范围,因为它对于b来说太短了,所以编译器出错了。

通过解耦生命周期,所有变量都有可能拥有自己的生命周期,一切都按预期工作。由于问题仅从D开始,因此足以在此时引入额外的生命周期。

struct A;

fn init_a() -> A {
    A {}
}

struct B<'a> {
    ed: &'a mut A,
}

fn init_b(ed: &mut A) -> B {
    B { ed }
}

struct C<'b> {
    pd: &'b mut B<'b>,
}

fn init_c<'b>(pd: &'b mut B<'b>) -> C<'b> {
    C { pd }
}

struct D<'c, 'b: 'c> {
    sd: &'c mut C<'b>,
}

fn init_d<'c, 'b: 'c>(sd: &'c mut C<'b>) -> D<'c, 'b> {
    D { sd }
}

fn main() {
    let mut a = init_a();
    let mut b = init_b(&mut a);
    let mut c = init_c(&mut b);
    let d = init_d(&mut c);
}

Playground link