谁借用了一个变量?

12

我正在与借用检查器作斗争。我有两个类似的代码片段,一个像我预期的那样工作,另一个则不是。

能够按照我的期望工作的代码:

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]
    }
}

我希望我能像情况1那样两次调用Bar2::f,而不会惹恼编译器。

问题在注释(3)中:是谁借走了bar2,而没有任何影响?

这是我的理解:

  1. 在情况1中,f2调用:'a生命周期参数是接收到的&Foo值的生命周期,因此当没有任何影响时,此生命周期为空,并且在Bar1::f调用后bar没有被借用;

  2. 在情况2中,bar2借用foo(不可变),因此Bar2结构体中的生命周期参数'bfoo引用的生命周期,它在f4函数体末尾结束。调用Bar2::f将借用bar2,并持续到f4函数体末尾。

但问题仍然是:是谁借走了bar2?可能是Bar2::f吗?Bar2::f如何在调用后保持借用所有权?我漏掉了什么?

我使用的是Rust 1.14.0-nightly (86affcdf6 2016-09-28) on x86_64-pc-windows-msvc。

4个回答

10

啊...你基本上是自我借用了。

问题在于你对FooBar的寿命使用了相同的寿命('b)。编译器会将这些寿命统一起来,最终导致一种奇怪的情况,在这种情况下,本应该在语句结束时结束的借用的寿命却在值应该超出范围之后才结束。

作为一个经验法则: 总是self使用新的生命周期。其他任何方法都有点奇怪。


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


由于这个有价值的经验法则,被接受了。谢谢。 - jferard

9
在第二种情况下,您有以下内容:
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

这段代码可以编译,编译器能够正确地理解对bar2foo的引用具有不同的生命周期。

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

简而言之,f4的自身引用和返回的引用具有相同的生命周期,则整个f4的作用域保留了对bar2的可变借用。

1
我希望在这里补充关于子类型/变异角色的内容。

&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>的引用。编译器会查看是否可以“临时缩短”bar2的生命周期,以适应函数f的作用域,例如'x,就好像在调用f之前创建了bar2foo,并在f之后立即消失(即临时缩短:假设变量bar2'x中创建,因此将Bar2<'a>转换为Bar2<'x>,其中'a是原始(真实)生命周期)。但是,在这里,“缩短”是不可能的;首先,由于对self的可变引用,它不能将Bar2<'a>转换为Bar2<'x>,因为&mut Bar2<'a>&mut Bar2<'x>相互不变(请记住,即使T < UT > U,那么&mut T&mut U相互不变)。因此,编译器必须使用Bar2<'a>;其次,由于函数fBar2Fooself)的引用具有相同的生命周期,不能将&'a Bar2<'a>转换为&'x Bar2<'a>。因此,这意味着在调用函数f时,引用并没有被“缩短”,它们将保持有效直到块的结尾。

如果省略了self的生命周期,那么编译器将为self提供一个新的生命周期(与'b不相关),这意味着它可以“暂时缩短”Bar2的生命周期,然后将其mut引用传递给f。即它会把&'a mut Bar2<'a>变成&'x mut Bar2<'a>,然后再传递给f。(请记住&'lifetime mut'lifetime上是协变的),因此它能够正常工作。

1

我将 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)。这就是为什么您无法重新借用它的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接