Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

rust - Why is a reference variable accessed via auto-deref moved?

I thought I got the idea of move semantics until this code.

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

I passed a reference, and I accessed a field via auto-deref feature, so why is it a move?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

What you are doing is field accessing on pointer.

Check Field Access Expression :

if the type of the expression to the left of the dot is a pointer, it is automatically dereferenced as many times as necessary to make the field access possible

Sample for how Rust evaluates Field Access Expression on Borrowed Content :

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

Note : d is still borrowed content, auto deref is just needed to access the fields.


Why move ?

Consider this code:

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

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

This code will work as expected and there will be no moves in here so you will able to reuse d.id. In this situation:

  1. Rust will try to copy the value of d.id. Since d.id is i32 and implements the Copy trait, it will copy the value to id.

Consider this code:

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

This code will not work because:

  1. Rust will try to copy d.body but Vec<i32> has no implementation of the Copy trait.
  2. Rust will try to move body from d, and you will get the "cannot move out of borrowed content" error.

How does this effect the loop?

From the reference

A for expression is a syntactic construct for looping over elements provided by an implementation of std::iter::IntoIterator

A for loop is equivalent to the following block expression.

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

is equivalent to

{
    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
}

This means your vector must have an implementation of IntoIterator because IntoIterator::into_iter(self) expects self as an argument. Luckily, both impl IntoIterator for Vec<T>, another is impl<'a, T> IntoIterator for &'a Vec<T> exist.

Why does this happen?

Simply:

  • When you use &d.body, your loop uses the &Vec implementation of IntoIterator.

This implementation returns an iterator which points at your vector's slice. This means you will get the reference of elements from your vector.

  • When you use d.body, your loop uses the Vec implementation of IntoIterator.

This implementation returns an iterator which is a consuming iterator. This means your loop will have the ownership of actual elements, not their references. For the consuming part this implementation needs the actual vector not the reference, so the move occurs.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...