首页 文章

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

提问于
浏览
1

我以为我有了移动语义的想法,直到这段代码 .

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

我通过了一个引用,我通过auto-deref功能访问了一个字段,为什么它是一个移动?

3 回答

  • -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仍然是借来的内容,只需要auto deref来访问这些字段 .


    为什么要搬家?

    考虑以下代码:

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

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

    • Rust将尝试复制 d.id 的值 . 由于 d.idi32 并实现 Copy 特征,因此它会将值复制到 id .

    考虑以下代码:

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

    此代码无效,因为:

    • Rust将尝试复制 d.body ,但 Vec<i32> 没有 Copy 特征的实现 .

    • Rust将尝试从 d 移动 body ,您将收到"cannot move out of borrowed content"错误 .

    这对循环有何影响?

    来自the reference

    for表达式是一个语法结构,用于循环由std :: iter :: IntoIterator的实现提供的元素.for循环等效于以下块表达式 . 'label:用于iter_expr中的PATTERN {
    / 循环体 /
    }
    相当于{
    let result = match IntoIterator :: into_iter(iter_expr){
    mut iter =>'label:loop {
    下一步让mut;
    匹配Iterator :: next(&mut iter){
    Option :: Some(val)=> next = val,
    选项::无=>休息,
    };
    让PAT =下一个;
    let()= {/ * loop body * /};
    },
    };
    结果
    }

    这意味着您的向量必须具有 IntoIterator 的实现,因为 IntoIterator::into_iter(self) 期望 self 作为参数 . 幸运的是,两个impl IntoIterator for Vec<T>,另一个是impl<'a, T> IntoIterator for &'a Vec<T>存在 .

    为什么会这样?

    只是:

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

    此实现返回一个指向矢量切片的迭代器 . 这意味着你将从你的向量中获得 reference of elements .

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

    此实现返回一个迭代器,它是一个消耗迭代器 . 这意味着你的循环将具有 ownership of actual elements ,而不是它们的引用 . 对于消费部分,此实现需要实际向量而不是引用,因此移动发生 .

  • 4

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

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

  • 0

    这个循环将是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 .

相关问题