Как изменить два поля одновременно в структуре 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;
}