为什么通过auto-deref访问的参考变量被移动了?

时间:2018-12-09 13:19:08

标签: rust

我以为直到此代码之前我都想到了移动语义。

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

我通过了引用,并且通过自动取消引用功能访问了一个字段,所以为什么要这样做?

3 个答案:

答案 0 :(得分:4)

您正在做的是在指针上进行字段访问。

检查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仍是借来的内容,仅需自动deref即可访问字段。


为什么要走?

考虑以下代码:

struct Data {
    body: Vec<i32>,
    id: i32,
}

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

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

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

考虑以下代码:

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

此代码将不起作用,因为:

  1. Rust将尝试复制d.body,但是Vec<i32>没有实现Copy特性。
  2. Rust将尝试将bodyd中移出,您将收到“无法移出借用的内容”错误。

这如何影响循环?

来自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<T>和另一个impl<'a, T> IntoIterator for &'a Vec<T>都存在。

为什么会这样?

简单地:

  • 使用&d.body时,循环使用&Vec的{​​{1}}实现。

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

  • 使用IntoIterator时,循环使用d.body的{​​{1}}实现。

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

答案 1 :(得分:0)

您正在访问body中的字段dbody本身是Vec<i32>,不是参考。如果直接使用d,则不需要&,但是由于要访问d中的字段,因此必须指定要引用该字段的内容。 / p>

基本上d拥有body。如果您借用d,则无法盗用body,它属于d,但您可以借用。

答案 2 :(得分:-1)

此循环为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