谁借了一个变量?

时间:2016-10-03 08:20:25

标签: rust lifetime borrow-checker

我和借阅检查员打架。我有两个相似的代码片段,一个像我期望的那样工作,另一个没有。

符合我期望的那个:

mod case1 {
    struct Foo {}

    struct Bar1 {
        x: Foo,
    }

    impl Bar1 {
        fn f<'a>(&'a mut self) -> &'a Foo {
            &self.x
        }
    }

    // only for example
    fn f1() {
        let mut bar = Bar1 { x: Foo {} };
        let y = bar.f(); // (1) 'bar' is borrowed by 'y'
        let z = bar.f();  // error (as expected) : cannot borrow `bar` as mutable more
                           // than once at a time [E0499]
    }

    fn f2() {
        let mut bar = Bar1 { x: Foo {} };
        bar.f(); // (2) 'bar' is not borrowed after the call
        let z = bar.f();  // ok (as expected)
    }
}

没有:

mod case2 {
    struct Foo {}

    struct Bar2<'b> {
        x: &'b Foo,
    }

    impl<'b> Bar2<'b> {
        fn f(&'b mut self) -> &'b Foo {
            self.x
        }
    }

    fn f4() {
        let foo = Foo {};
        let mut bar2 = Bar2 { x: &foo };
        bar2.f(); // (3) 'bar2' is borrowed as mutable, but who borrowed it?
        let z = bar2.f(); // error: cannot borrow `bar2` as mutable more than once at a time [E0499]
    }
}

我希望我可以在不刺激编译器的情况下两次调用Bar2::f,如案例1所示。

问题在评论(3)中:谁借了bar2,而没有做作?

以下是我的理解:

  1. 在案例1中,f2调用:生命周期参数'a是接收&Foo值之一,因此当没有做作时,此生命周期为空,并且bar来电后,我不会借用Bar1::f

  2. 在案例2中,bar2借用foo(作为不可变),因此'b结构中的生命周期参数Bar2foo引用终身,终结于f4身体的末尾。致电Bar2::f借用bar2生命周期,即f4结尾。

  3. 但问题仍然是:谁借了bar2?可能是Bar2::f吗?电话结束后Bar2::f如何保留借来的所有权?我在这里缺少什么?

    我在x86_64-pc-windows-msvc上使用Rust 1.14.0-nightly(86affcdf6 2016-09-28)。

4 个答案:

答案 0 :(得分:9)

如果是#2,你有这个:

impl<'b> Bar2<'b> {
    fn f(&'b mut self) -> &'b Foo {
        self.x
    }
}

要突出显示:&'b mut self&'b Foo具有相同的生命周期。

这就是说self的引用和对Foo实例的返回引用都具有相同的生命周期。查看呼叫站点,您有:

let foo = Foo {};
let mut bar2 = Bar2 { x: &foo };

因此,编译器推断foobar2具有相同的生命周期。 foo的生命周期是f4函数的范围,因此bar2的可变引用与此共享。

解决此问题的一种方法是删除self引用的显式生命周期:

fn f(&mut self) -> &'b Foo

这编译并且编译器正确理解对bar2的引用和对foo的引用具有不同的生命周期。

游乐场:https://play.rust-lang.org/?gist=caf262dd628cf14cc2884a3af842276a&version=stable&backtrace=0

TLDR:是的,在自引用和返回的引用上具有相同的生命周期说明符意味着f4的整个范围持有bar2的可变借位。

答案 1 :(得分:9)

啊......你基本上是自己借来的。

问题取决于您在'b的生命周期和Foo的生命周期内使用相同的生命周期(Bar)。然后编译器尽职地统一这些生命周期,最后你会陷入一种奇怪的情况,在这种情况下,应该在语句结束时结束的借用的生命周期在值超出范围之后结束。

根据经验法则:总是self使用新的生命周期。其他任何事情都很奇怪。

值得注意的是,这种模式实际上可能很有用(尽管更可能使用不可变借用):它允许将值锚定到堆栈帧,从而阻止任何移动调用函数,它(有时)用于表示Rust没有很好建模的借位(比如将指向值的指针传递给FFI)。

答案 2 :(得分:1)

我将f4()的正文放在main()中并为Drop实施Bar2以找出它何时被删除(即超出范围):

impl<'b> Drop for Bar2<'b> {
    fn drop(&mut self) { println!("dropping Bar2!"); }
}

结果是:

error: `bar2` does not live long enough
  --> <anon>:24:5
   |
24 |     bar2.f();
   |     ^^^^ does not live long enough
25 | }
   | - borrowed value dropped before borrower
   |
   = note: values in a scope are dropped in the opposite order they are created

有些东西可疑;让我们用辅助范围详细检查它:

fn main() {
    {
        let foo = Foo {}; // foo scope begins
        {
            let mut bar2 = Bar2 { x: &foo }; // bar2 scope begins; bar2 borrows foo
            bar2.f();
        } // bar2 should be dropped here, but it has the same lifetime as foo, which is still live
    } // foo is dropped (its scope ends)
}

我认为此处存在泄漏,bar2永不丢弃(因此Drop无法实现)。这就是为什么你不能重新借用它。

答案 3 :(得分:1)

我想补充一下子类型化/差异化在这里扮演的角色。

&mut TT上是不变的。给定两种类型TU,其中T <UTU的子类型),然后&mut T&mut U没有子类型关系(即它们彼此不变),而&T&U的子类型(&T <&U)。但是&'lifetime&'lifetime mut相对于'lifetime是协变的。因此,给定类型'a的两个生存期'bT,其中'a的生存期'b,然后根据子类型关系&'a T <{{ 1}},类似&'b T <&'a mut T

提到这个问题,在对函数&'b mut T的调用中,f是对self的引用。编译器将查看其是否可以“暂时缩短” Bar2<'a>的寿命以适合函数bar2的调用范围f,就像'xbar2刚好在调用foo之前创建,并在f之后立即消失(即临时缩短:假定在f内创建变量bar2并因此在{{1 }}到'x,其中Bar2<'a>是原始(实际)生存期)。但是在这里,“缩短”是不可能的。一种是由于对Bar2<'x>的可变引用,另一种是由于在函数'a'中对selfFooBar2)的引用具有相同的生存期的定义。首先,由于它是可变引用,因此无法将self转换为f,因为Bar2<'a>Bar2<'x>彼此不变。 (请记住,即使&mut Bar2<'a>&mut Bar2<'x>T < U对于T > U也是不变的)。因此,编译器必须使用&mut T,其次,由于函数&mut U具有相同的生存期来引用Bar2<'a>f,因此无法转换{{1 }}到Bar2。因此,这意味着在调用函数Foo时引用不会被“缩短”,它们将一直有效到块末。

如果忽略了&'a Bar2<'a>的生存期,则编译器将为&'x Bar2<'a>提供新的生存期(与f脱节),这意味着可以“暂时缩短” self的寿命,然后将其self引用传递给'b。即它将Bar2mut,然后将其传递到f。 (请记住,&'a mut Bar2<'a>&'x mut Bar2<'a>是协变的),因此它将起作用。