Почему компилятор Rust жалуется, что я использую перемещенное значение, когда я заменил его новым значением? - PullRequest
3 голосов
/ 18 февраля 2020

Я работаю над двумя односвязными списками с именами longer и shorter. Длина longer one гарантированно будет не меньше, чем shorter one.

Я разбиваю списки поэлементно и что-то делаю с каждой парой. Если список longer содержит больше непарных элементов, обработайте остальные из них:

struct List {
    next: Option<Box<List>>,
}

fn drain_lists(mut shorter: Option<Box<List>>, mut longer: Option<Box<List>>) {
    // Pair the elements in the two lists.
    while let (Some(node1), Some(node2)) = (shorter, longer) {
        // Actual work elided.
        shorter = node1.next;
        longer = node2.next;
    }
    // Process the rest in the longer list.
    while let Some(node) = longer {
        // Actual work elided.
        longer = node.next;
    }
}

Однако компилятор жалуется на второй, а l oop, что

error[E0382]: use of moved value
  --> src/lib.rs:13:20
   |
5  | fn drain_lists(mut shorter: Option<Box<List>>, mut longer: Option<Box<List>>) {
   |                                                ---------- move occurs because `longer` has type `std::option::Option<std::boxed::Box<List>>`, which does not implement the `Copy` trait
6  |     // Pair the elements in the two lists.
7  |     while let (Some(node1), Some(node2)) = (shorter, longer) {
   |                                                      ------ value moved here
...
13 |     while let Some(node) = longer {
   |                    ^^^^ value used here after move

Однако , Я do устанавливаю новое значение для shorter и longer в конце l oop, так что я никогда не буду использовать их перемещенное значение.

Как я должен обслуживать компилятор?

Ответы [ 4 ]

4 голосов
/ 18 февраля 2020

Я думаю, что проблема вызвана временным кортежем в первом l oop. Создание кортежа перемещает его компоненты в новый кортеж, и это происходит, даже если последующее сопоставление с образцом не удается.

Во-первых, позвольте мне написать более простую версию вашего кода. Это прекрасно компилируется:

struct Foo(i32);
fn main() {
    let mut longer = Foo(0);
    while let Foo(x) = longer {
        longer = Foo(x + 1);
    }
    println!("{:?}", longer.0);
}

Но если я добавлю временное к while let, я вызову ошибку компилятора, похожую на вашу:

fn fwd<T>(t: T) -> T { t }
struct Foo(i32);
fn main() {
    let mut longer = Foo(0);
    while let Foo(x) = fwd(longer) {
        longer = Foo(x + 1);
    }
    println!("{:?}", longer.0);
    //        Error: ^ borrow of moved value: `longer`
}

Решение состоит в том, чтобы добавить локальная переменная со значением, подлежащим деструктуризации, вместо использования временной. В вашем коде:

struct List {
    next: Option<Box<List>>
}

fn drain_lists(shorter: Option<Box<List>>,
               longer: Option<Box<List>>) {
    // Pair the elements in the two lists.
    let mut twolists = (shorter, longer);
    while let (Some(node1), Some(node2)) = twolists {
        // Actual work elided.
        twolists = (node1.next, node2.next);
    }
    // Process the rest in the longer list.
    let (_, mut longer) = twolists;
    while let Some(node) = longer {
        // Actual work elided.
        longer = node.next;
    }
}
1 голос
/ 18 февраля 2020

Помимо избавления от кортежа (показанного другими), вы можете перехватить изменяемую ссылку на узлы:

    while let (&mut Some(ref mut node1), &mut Some(ref mut node2)) = (&mut shorter, &mut longer) {
        shorter = node1.next.take();
        longer = node2.next.take();
    }

Использование take() позволяет это работать: shorter = node1.next будет пожаловаться на перемещение поля из ссылки, что недопустимо (из-за этого node останется в неопределенном состоянии). Но take это нормально, потому что оно оставляет None в поле next.

0 голосов
/ 18 февраля 2020

Одно из решений - избежать кортежа и, следовательно, больше двигаться в кортеж.

fn actual_work(node1: &Box<List>, node2: &Box<List>) {
    // Actual work elided
}

fn drain_lists(mut shorter: Option<Box<List>>, mut longer: Option<Box<List>>) {
    while let Some(node1) = shorter {

        if let Some(node2) = longer.as_ref() {
            actual_work(&node1, node2);
        }

        shorter = node1.next;
        longer = longer.map_or(None, move |l| {
            l.next
        });
    }
    // Process the rest in the longer list.

    while let Some(node) = longer {
        // Actual work elided.
        longer = node.next;
    }

}
0 голосов
/ 18 февраля 2020

Похоже, что деструктуризация в строке 7 перемещает значение, даже если блок впоследствии не оценивается. (Правка: как отметил @Sven Marnach в комментариях, здесь создается временный кортеж, который вызывает движение) Я испортил ваш код, чтобы доказать эту точку:)

struct List {
    next: Option<Box<List>>
}

fn drain_lists(mut shorter: Option<Box<List>>,
               mut longer: Option<Box<List>>) {
    // Pair the elements in the two lists.
    match(shorter, longer) {
        (Some(node1), Some(node2)) => {
            shorter = node1.next;
            longer = node2.next;
        },
        (_, _) => return  // without this you get the error
    }
    // Process the rest in the longer list.
    while let Some(node) = longer {
        // Actual work elided.
        longer = node.next;
    }
} 

Когда я добавил возврат для случая по умолчанию код скомпилирован.

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