为什么移动了通过自动取消引用访问的引用变量?

Jac*_*cob 2 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>,
}
Run Code Online (Sandbox Code Playgroud)
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>,
}
Run Code Online (Sandbox Code Playgroud)

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

Öme*_*den 5

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

检查字段访问表达式

如果点左边的表达式类型是指针,它会根据需要自动取消引用多次,以使字段访问成为可能

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

let d = Data { /*input*/}
let body = (&d).body // -> (*&d).body -> d.body
let ref_body = &(&d).body // -> &(*&).body -> &d.body -> &(d.body) 
Run Code Online (Sandbox Code Playgroud)

注意 : d 仍然是借用的内容,只需要 auto deref 来访问字段。


为什么要动?

考虑这个代码:

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

fn p(mut d: &Data) {
    let id = d.id;
}
Run Code Online (Sandbox Code Playgroud)

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

  1. Rust 会尝试复制d.id. 由于d.idisi32并实现了Copytrait,它会将值复制到id.

考虑这个代码:

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

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

  1. Rust 会尝试复制d.bodyVec<i32>没有实现Copytrait。
  2. Rust 将尝试body从 移出d,您将收到“无法移出借用的内容”错误。

这如何影响循环?

参考

for表达是遍历元件语法构造通过的实施方案提供std::iter::IntoIterator

for 循环等效于以下块表达式。

'label: for PATTERN in iter_expr {
    /* loop body */
}
Run Code Online (Sandbox Code Playgroud)

相当于

{
    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
}
Run Code Online (Sandbox Code Playgroud)

这意味着你的载体必须具备的一个实现IntoIterator,因为IntoIterator::into_iter(self)预计self作为参数。幸运的是,两者都 存在impl IntoIterator for Vec<T>,另一个impl<'a, T> IntoIterator for &'a Vec<T>存在。

为什么会发生这种情况?

简单地:

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

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

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

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