谁借用了一个变量?

Who borrowed a variable?

我在和借阅检查员吵架。我有两段相似的代码,一段按我的预期工作,另一段没有。

符合我预期的那个:

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调用:lifetime参数'a是接收&Foo值的一个,所以当有时这个lifetime为空不做作,Bar1::f调用后bar不借用;

  2. 在情况 2 中,bar2 借用了 foo(因为不可变),因此 Bar2 结构中的生命周期参数 'bfoo 引用生命周期,在 f4 主体结束时结束。调用 Bar2::f 在该生命周期内借用 bar2,即到 f4.

  3. 结束

但问题仍然是:谁借了bar2?难道是Bar2::fBar2::f 调用后如何持有借用的所有权?我在这里错过了什么?

我在 x86_64-pc-windows-msvc.

使用 Rust 1.14.0-nightly (86affcdf6 2016-09-28)

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

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 无法实现)。这就是为什么你不能重新借用它。

在案例 #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:是的,在 self 引用和返回的引用上具有相同的生命周期说明符意味着 f4 的整个范围持有 bar2.

的可变借用

啊...你基本上是自借的

这个问题取决于您有相同的生命周期 ('b) 用于 Foo 的生命周期和 Bar 的生命周期。然后编译器尽职尽责地统一了这些生命周期,你最终会遇到一种奇怪的情况,即本应在语句末尾结束的借用生命周期突然在值应该超出范围之后结束。

根据经验:始终 使用新的生命周期 self。其他的都很奇怪。


有趣的是,这种模式实际上很有用(尽管更可能是不可变借用):它允许锚定一个值到堆栈帧,防止之后的任何移动对函数的调用,这(有时)可用于表示 Rust 未很好建模的借用(例如将指向值的指针传递给 FFI)。

我想补充一下 subtyping/variance 在这里扮演的角色。

&mut TT 上是不变的。给定两个类型 TU,其中 T < UTU 的子类型),那么 &mut T&mut U 没有子类型关系(即它们彼此不变),而 &T&U 的子类型(&T < &U)。但是 &'lifetime&'lifetime mut 都在 'lifetime 上是协变的。因此,给定类型 T 的两个生命周期 'a'b,其中 'a'b 长,然后根据子类型关系 &'a T < &'b T,类似地 &'a mut T < &'b mut T

问题来了,在函数 f 的调用中,self 是对 Bar2<'a> 的引用。编译器将查看它是否可以 "temporarily shorten" bar2 的生命周期来适应函数 f 的调用范围 'x,就好像 bar2 foo 是在调用 f 之前创建的,并在 f 之后立即消失(即临时缩短:假设变量 bar2'x 中创建,因此 Bar2<'a>Bar2<'x>'a 是原始(实际)生命周期)。但是在这里,"shortening"是不可能的;一是因为对 self 的可变引用,二是在函数 f 中对 FooBar2 (self) 的引用具有相同的生命周期定义。首先,由于它是一个可变引用,它不能将Bar2<'a> 转换为Bar2<'x>,因为&mut Bar2<'a>&mut Bar2<'x> 是相互不变的。 (请记住,即使 T < UT > U,那么 &mut T&mut U 也是不变的)。因此编译器必须使用 Bar2<'a>,其次,由于函数 fBar2Foo 的引用具有相同的生命周期,因此无法转换 &'a Bar2<'a>&'x Bar2<'a>。所以这意味着在调用函数 f 时引用不是 "shortened" 并且它们将保持有效直到块结束。

如果 self 的生命周期被省略,那么编译器会给 self 一个新的生命周期(与 'b 不相交),这意味着它可以自由地 "temporarily shorten" Bar2 的生命,然后将它的 mut 引用传递给 f。即它将执行 &'a mut Bar2<'a>&'x mut Bar2<'a>,然后将其传递给 f。 (记住 &'lifetime mut'lifetime 上是协变的)因此它会起作用。