【问题标题】:Who borrowed a variable?谁借用了变量?
【发布时间】:2017-02-11 03:22:32
【问题描述】:

我正在与借阅检查器作斗争。我有两段类似的代码,一段按预期工作,另一段没有。

按我预期工作的那个:

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是接收&amp;Foo值的那个,所以这个lifetime在没有做作的情况下是空的,bar不是借用的在Bar1::f 通话之后;

  2. 在情况2中,bar2借用foo(作为不可变的),所以Bar2结构中的生命周期参数'b是foo引用生命周期,它在@987654335的末尾结束@ 身体。调用 Bar2::f 在该生命周期内借用 bar2,即到 f4 的末尾。

但问题仍然存在:谁借了bar2?会不会是Bar2::f?通话后Bar2::f 将如何持有借来的所有权?我在这里错过了什么?

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

【问题讨论】:

    标签: rust lifetime borrow-checker


    【解决方案1】:

    啊……你基本上是自己借的。

    问题在于,Foo 的生命周期和 Bar 的生命周期使用相同的生命周期 ('b)。然后编译器尽职尽责地统一这些生命周期,您最终会陷入一种奇怪的情况,突然间本应在语句末尾结束的借用生命周期却在值超出范围后结束。

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


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

    【讨论】:

    • 接受这个宝贵的经验法则。谢谢。
    【解决方案2】:

    在第 2 种情况下,您有这个:

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

    突出显示:&amp;'b mut self 和 &amp;'b Foo 指定了相同的生命周期。

    这意味着对self 的引用和对Foo 实例的返回引用都具有相同的生命周期。查看调用站点,您会发现:

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

    因此编译器推断foo 和bar2 具有相同的生命周期。 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 的可变借用。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

        &amp;mut T 在T 上是不变的。给定T 和U 两种类型,其中T U(T 是U 的子类型),那么&amp;mut T 与&amp;mut U 没有子类型关系(即它们与彼此),而&amp;T 是&amp;U 的子类型(&amp;T &U)。但是&amp;'lifetime 和&amp;'lifetime mut,两者都比'lifetime 协变。所以给定两个生命周期 'a 和 'b 对于类型 T,其中 'a 比 'b 长,然后根据子类型关系 &amp;'a T &'b T,类似地 &amp;'a mut T &'b mut T

        回到问题,在对函数f 的调用中,self 是对Bar2&lt;'a&gt; 的引用。编译器将查看它是否可以“暂时缩短”bar2 的生命周期以适应函数 f 的调用范围,例如 'x,就好像 bar2 和 foo 是在 @ 之前创建的一样987654355@ 被调用并在f 之后立即消失(即临时缩短:假设变量bar2 在'x 中创建,因此Bar2&lt;'a&gt; 到Bar2&lt;'x&gt;,'a 是原始(真实)生命周期)。但在这里,“缩短”是不可能的;一,因为对self 的可变引用和二,在函数f 的定义中对@9​​87654363@ 和Bar2 (self) 的引用具有相同的生命周期。首先,由于它是一个可变引用,它不能将Bar2&lt;'a&gt; 转换为Bar2&lt;'x&gt;,因为&amp;mut Bar2&lt;'a&gt; 和&amp;mut Bar2&lt;'x&gt; 彼此是不变的。 (记住即使T &lt; U 或T &gt; U,那么&amp;mut T 与&amp;mut U 是不变的)。因此编译器必须使用Bar2&lt;'a&gt;,其次,由于函数f 对Bar2 和Foo 的引用具有相同的生命周期,因此无法将&amp;'a Bar2&lt;'a&gt; 转换为&amp;'x Bar2&lt;'a&gt;。因此,这意味着在调用函数 f 时引用不会“缩短”,并且它们将保持有效直到块结束。

        如果self 的生命周期被省略,那么编译器会给self 一个新的生命周期(与'b 不相交),这意味着可以随意“暂时缩短”Bar2 的生命周期然后将mut 的引用传递给f。即它会将&amp;'a mut Bar2&lt;'a&gt; 传递给&amp;'x mut Bar2&lt;'a&gt;,然后将其传递给f。 (记住&amp;'lifetime mut 与'lifetime 是协变的),因此它会起作用。

        【讨论】:

          猜你喜欢
          • 2022-11-26
          • 1970-01-01
          • 2015-10-16
          • 2015-08-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多