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

Рассмотрим следующий простой пример:

pub struct Bar {
    curr: Vec<i32>,
    prev: Vec<i32>,
}

pub fn main() {
    let mut b = Bar { curr: vec![1, 2, 3], prev: vec![2, 3, 4] };

    foo(&mut b);
}

pub fn foo(bar: &mut Bar) {
    let next = vec![3, 4, 5];

    bar.prev = bar.curr;
    bar.curr = next;
}

Использование Vec не имеет значения; Дело в том, что Bar имеет два поля, которые не реализуют Copy, Это не компилируется:

error[E0507]: cannot move out of borrowed content
  --> derp.rs:15:16
   |
15 |     bar.prev = bar.curr;
   |                ^^^ cannot move out of borrowed content

Нетрудно понять почему: двигаясь bar.curr не заменяя его немедленно, мы должны двигаться bar сам, что нам не разрешено делать, так как он только заимствован, а на самом деле не принадлежит.

Однако это очень распространенный вариант использования (в данном случае - например, сохранение двух последних выходов функции), и я чувствую, что должен быть идиоматический вариант использования Rust. Я понимаю, что это можно обойти, используя один кортеж (curr, prev) и назначая его сразу, но (предполагая функцию foo был написан после структуры Bar был в использовании) рефакторинг может быть довольно сложно.

Присвоение двух значений одновременно не кажется законным: код (bar.prev, bar.curr) = (bar.curr, next) не компилируется, поскольку левая сторона не является допустимым значением левой стороны.

Несколько интересно, что следующий код компилируется:

pub struct Bar {
    curr: Vec<i32>,
    prev: Vec<i32>,
}

pub fn main() {
    let b = Bar { curr: vec![1, 2, 3], prev: vec![2, 3, 4] };

    foo(b);
}

pub fn foo(mut bar: Bar) -> Bar {
    let next = vec![3, 4, 5];

    bar.prev = bar.curr;
    bar.curr = next;

    bar
}

В то время как линия bar.prev = bar.curr Кажется, требуются права на перемещение, но, похоже, они не используются, как показано в следующей строке bar.curr = next не должен компилироваться, если bar был перемещен

Кроме того, если вы берете bar.curr = next строка больше не компилируется (bar возвращается после перемещения), поэтому кажется, что компилятор достаточно умен, чтобы выяснить, как решить эту проблему (чтобы все поля в конечном итоге были стабильно назначены), но не может выполнить ту же задачу для изменяемых указателей.

Итак, я думаю (а) это ошибка, (б) это известная ошибка, и (в) есть ли обходной путь, так что я все еще могу сделать это с изменяемыми указателями?

2 ответа

Решение

Использование std::mem::replace или же std::mem::swap,

pub fn foo(bar: &mut Bar) {
    use std::mem;
    let next = vec![3, 4, 5];
    bar.prev = mem::replace(&mut bar.curr, next);
}

Несколько интересно, что следующий код компилирует [...]

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

Это не ошибка, просто работает Rust.

Ты можешь использовать std::mem::swap:

pub fn foo(bar: &mut Bar) {
    let next = vec![3, 4, 5];

    std::mem::swap(&mut bar.prev, &mut bar.curr);
    bar.curr = next;
}
Другие вопросы по тегам