В чем разница между сопоставлением изменяемой ссылки на параметр в «if let Some (ref mut x) = option» и в «if let Some (x) = option.as_mut ()»? - PullRequest
9 голосов
/ 13 июля 2020

Фон

Рассмотрим игрушечную задачу, в которой у меня есть структура Node, представляющая узлы связанных списков, и я хочу создать функцию, которая строит список со значениями от 1 до 9. Следующее код работает должным образом:

struct Node {
    val: i32,
    next: Option<Box<Node>>,
}

fn build_list() -> Option<Box<Node>> {
    let mut head = None;
    let mut tail = &mut head;
    for n in 1..10 {
        *tail = Some(Box::new(Node {val: n, next: None}));
        if let Some(ref mut x) = tail {
            tail = &mut x.next;
        };
    }
    head
}

Но если я изменю выражение соответствия в функции build_list следующим образом, он не сможет скомпилировать:

fn build_list() -> Option<Box<Node>> {
    let mut head = None;
    let mut tail = &mut head;
    for n in 1..10 {
        *tail = Some(Box::new(Node {val: n, next: None}));
        if let Some(x) = tail.as_mut() {
            tail = &mut x.next;
        };
    }
    head
}

Ошибка компиляции:

error[E0506]: cannot assign to `*tail` because it is borrowed
  --> src/main.rs:72:9
   |
72 |         *tail = Some(Box::new(Node {val: n, next: None}));
   |         ^^^^^
   |         |
   |         assignment to borrowed `*tail` occurs here
   |         borrow later used here
73 |         {
74 |             if let Some(x) = tail.as_mut() {
   |                              ---- borrow of `*tail` occurs here

error[E0499]: cannot borrow `*tail` as mutable more than once at a time
  --> src/main.rs:74:30
   |
74 |             if let Some(x) = tail.as_mut() {
   |                              ^^^^ mutable borrow starts here in previous iteration of loop

Вопрос

В этом примере, в чем разница между

if let Some(ref mut x) = tail

и

if let Some(x) = tail.as_mut()

?

(Как новичок, изучающий Rust) я ожидал, что эти выражения совпадения будут эквивалентными, но, очевидно, есть некоторая тонкая разница, которую мне не хватает.

Обновление

Я очистил код от моего оригинала пример, так что мне не нужен элемент-заполнитель для заголовка списка. Разница (и ошибка компиляции) все еще остается, я просто получаю дополнительную ошибку компиляции для присвоения заимствованному *tail.

Обновление 2

(Это просто любопытное наблюдение, не помогите ответить на исходный вопрос). После рассмотрения ответа @ Emoun стало важным (в первом рабочем примере), что компилятор должен знать, что tail изменяется на каждой итерации l oop (так что он может убедитесь, что &mut x.next заимствованные каждый раз были разными). Поэтому я провел эксперимент по изменению кода таким образом, чтобы компилятор не мог определить, так ли это, добавив условие if n % 2 == 0 к назначению tail = &mut x.next;. Разумеется, это привело к ошибке компиляции, аналогичной предыдущей:

fn build_list() -> Option<Box<Node>> {
    let mut head = None;
    let mut tail = &mut head;
    for n in 1..10 {
        *tail = Some(Box::new(Node {val: n, next: None}));
        if let Some(ref mut x) = tail {
            if n % 2 == 0 {
                tail = &mut x.next;
            }
        };
    }
    head
}

Новая ошибка:

error[E0506]: cannot assign to `*tail` because it is borrowed
  --> src/main.rs:60:9
   |
60 |         *tail = Some(Box::new(Node {val: n, next: None}));
   |         ^^^^^
   |         |
   |         assignment to borrowed `*tail` occurs here
   |         borrow later used here
61 |         if let Some(ref mut x) = tail {
   |                     --------- borrow of `*tail` occurs here

error[E0503]: cannot use `*tail` because it was mutably borrowed
  --> src/main.rs:61:16
   |
61 |         if let Some(ref mut x) = tail {
   |                ^^^^^---------^
   |                |    |
   |                |    borrow of `tail.0` occurs here
   |                use of borrowed `tail.0`
   |                borrow later used here

error[E0499]: cannot borrow `tail.0` as mutable more than once at a time
  --> src/main.rs:61:21
   |
61 |         if let Some(ref mut x) = tail {
   |                     ^^^^^^^^^ mutable borrow starts here in previous iteration of loop

1 Ответ

2 голосов
/ 14 июля 2020

Причина, по которой вторая версия вашего кода терпит неудачу, заключается в том, что методы / функция rust всегда заимствуют объекты целиком, а не их части.

В вашем случае это означает, что tail.as_mut() заимствует tail изменчиво, и что этот заимствование будет оставаться в силе, пока используется tail:

...
    for n in 1..10 {
        *tail = Some(Box::new(Node {val: n, next: None})); // Error in the second iteration,
                                                           // 'tail' was already borrowed
        if let Some(x) = tail.as_mut() { // <-+ Borrow of 'tail' starts in the first iteration
            tail = &mut x.next;          // <-+ 'tail' now borrows itself
        };                               //   |
    }                                    // <-+ Borrow of 'tail' ends here, after the last iteration
...

Поскольку x является заимствованием tail, &mut x.next также является заимствованием tail, что означает, что tail = &mut x.next - это tail заимствование. Следовательно, первоначальное заимствование tail не может go выходить за рамки, пока tail находится в сфере действия. tail используется на каждой итерации, поэтому заимствование может только go выйти за пределы области действия после последней итерации l oop.

Теперь, почему работает первая версия build_list? Вкратце: потому что tail никогда не заимствуется. if let Some(ref mut x) = tail - это деструктуризация tail на его компоненты (в данном случае Option::Some и x). Это не заимствует tail целиком, а просто заимствует x. Когда вы затем tail = &mut x.next, вы теперь также деструктурируете x на его компоненты (извлекая только next) и заимствуете их, используя tail. В следующей итерации tail не заимствуется и поэтому может быть успешно переназначен.

Вызов методов / функций ограничен тем, что они не знают, какие части объекта вы будете использовать позже. Следовательно, as_mut() должен заимствовать весь tail, даже если вы используете только его часть. Это ограничение системы типов и одна из причин, почему методы получения / установки более слабые / более ограничивающие, чем прямой вызов членов структуры: средства получения / установки заставят вас заимствовать всю структуру, в то время как прямой доступ к члену будет заимствовать только это член, а не другие.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...