【问题标题】:Why is a reference variable accessed via auto-deref moved?为什么通过 auto-deref 访问的引用变量被移动?
【发布时间】:2018-12-09 13:19:08
【问题描述】:

在这段代码之前,我以为我已经有了移动语义的想法。

fn main() {
    let v = Data {
        body: vec![10, 40, 30],
    };
    p(&v);
}

fn p(d: &Data) {
    for i in d.body {
        // &d.body, Why d.body move?
        println!("{}", i);
    }
}

struct Data {
    body: Vec<i32>,
}
error[E0507]: cannot move out of borrowed content
 --> src/main.rs:9:14
  |
9 |     for i in d.body {
  |              ^^^^^^ cannot move out of borrowed content

error[E0507]: cannot move out of `d.body` which is behind a `&` reference
 --> src/main.rs:9:14
  |
8 | fn p(d: &Data) {
  |         ----- help: consider changing this to be a mutable reference: `&mut Data`
9 |     for i in d.body {
  |              ^^^^^^
  |              |
  |              cannot move out of `d.body` which is behind a `&` reference
  |              `d` is a `&` reference, so the data it refers to cannot be moved

我传递了一个引用,并且我通过自动取消引用功能访问了一个字段,那么为什么会这样呢?

【问题讨论】:

    标签: rust


    【解决方案1】:

    你正在做的是指针上的字段访问。

    检查Field Access Expression:

    如果点左边的表达式的类型是指针,它 被自动取消引用多次,以使 可以进行现场访问

    Rust 如何评估借用内容上的字段访问表达式的示例:

    let d = Data { /*input*/}
    let body = (&d).body // -> (*&d).body -> d.body
    let ref_body = &(&d).body // -> &(*&).body -> &d.body -> &(d.body) 
    

    注意:d 仍然是借用的内容,只需要自动取消引用来访问字段。


    为什么要搬家?

    考虑这段代码:

    struct Data {
        body: Vec<i32>,
        id: i32,
    }
    
    fn p(mut d: &Data) {
        let id = d.id;
    }
    

    此代码将按预期工作,此处不会有任何移动,因此您可以重用 d.id。在这种情况下:

    1. Rust 将尝试复制d.id 的值。由于d.id 是i32 并实现了Copy 特征,它会将值复制到id。

    考虑这段代码:

    fn p(mut d: &Data) {
        let id = d.id; // works
        let body = d.body; // fails
    }
    

    此代码不起作用,因为:

    1. Rust 将尝试复制 d.body 但 Vec&lt;i32&gt; 没有实现 Copy 特征。
    2. Rust 会尝试将 body 从 d 移出,您将收到“无法移出借用内容”错误。

    这对循环有何影响?

    来自the reference

    for 表达式是一个句法结构,用于循环由std::iter::IntoIterator 的实现提供的元素

    for 循环等价于下面的块表达式。

    'label: for PATTERN in iter_expr {
        /* loop body */
    }
    

    等价于

    {
        let result = match IntoIterator::into_iter(iter_expr) {
            mut iter => 'label: loop {
                let mut next;
                match Iterator::next(&mut iter) {
                    Option::Some(val) => next = val,
                    Option::None => break,
                };
                let PAT = next;
                let () = { /* loop body */ };
            },
        };
        result
    }
    

    这意味着您的向量必须具有IntoIterator 的实现,因为IntoIterator::into_iter(self) 需要self 作为参数。幸运的是,impl IntoIterator for Vec&lt;T&gt;,另一个是impl&lt;'a, T&gt; IntoIterator for &amp;'a Vec&lt;T&gt; 都存在。

    为什么会这样?

    简单地说:

    • 当您使用&amp;d.body 时,您的循环将使用&amp;Vec 的IntoIterator 实现。

    此实现返回一个指向向量切片的迭代器。这意味着您将从向量中获得元素的引用。

    • 当您使用d.body 时,您的循环使用Vec 的IntoIterator 实现。

    这个实现返回一个迭代器,它是一个消费迭代器。这意味着您的循环将拥有实际元素的所有权,而不是它们的引用。对于消耗部分,此实现需要实际的向量而不是参考,因此会发生移动。

    【讨论】:

      【解决方案2】:

      您正在访问d 中的字段body。 body 本身就是 Vec&lt;i32&gt;,它不是参考。如果您直接使用d,则不需要&amp;,但由于您正在访问d 中的字段,因此您必须指定您想要对该字段的引用。

      基本上d 拥有body。如果你借d你不能偷body,它属于d但你可以借。

      【讨论】:

      • 如果您直接使用d,则不需要&amp;,但由于您访问的是d 中的字段,因此您必须指定您希望拥有该引用到现场。 - 这个答案没有描述为什么会发生这种情况。
      【解决方案3】:

      这个循环将是desugared into something similar to the following:

      let mut iter = IntoIterator::into_iter(v);
      loop {
          match iter.next() {
              Some(x) => {
                  // loop body
              },
              None => break,
          }
      }
      

      如您所见,它使用了into_iter,它移动了矢量d.body。

      【讨论】:

      • 这没有说明为什么d.body 是引用时移动了d。
      猜你喜欢
      • 2015-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-15
      • 2020-02-06
      • 2019-05-10
      • 2013-01-03
      • 2018-01-05
      相关资源
      最近更新 更多