Измените вариант перечисления при перемещении поля в новый вариант
Я хочу обновить перечислимый вариант при перемещении поля старого варианта на новое без клонирования:
enum X {
X1(String),
X2(String),
}
fn increment_x(x: &mut X) {
*x = match *x {
X::X1(s) => X::X2(s),
X::X2(s) => X::X1(s),
}
}
Это не работает, потому что мы не можем переместить s
из &mut X
:
error[E0507]: cannot move out of borrowed content
--> src/lib.rs:7:16
|
7 | *x = match *x {
| ^^
| |
| cannot move out of borrowed content
| help: consider removing the '*': 'x'
8 | X::X1(s) => X::X2(s),
| - data moved here
9 | X::X2(s) => X::X1(s),
| - ...and here
Пожалуйста, не предлагайте такие вещи, как реализация enum X { X1, X2 }
и использование struct S { variant: X, str: String }
и т.д. Это упрощенный пример, представьте себе, что в вариантах есть много других полей и вы хотите переместить одно поле из одного варианта в другой.
Ответы
Ответ 1
Это не работает, потому что мы не можем переместить s
из &mut X
Тогда не делайте этого... возьмите структуру по значению и верните новую:
enum X {
X1(String),
X2(String),
}
fn increment_x(x: X) -> X {
match x {
X::X1(s) => X::X2(s),
X::X2(s) => X::X1(s),
}
}
В конечном счете, компилятор защищает вас, потому что если бы вы могли вывести строку из перечисления, то она была бы в некоем полусозданном состоянии. Кто будет отвечать за освобождение строки, если функция будет паниковать в этот момент? Должен ли он освободить строку в enum или строку в локальной переменной? Это не может быть и то и другое, поскольку двойное освобождение - это проблема безопасности памяти.
Если бы вам пришлось реализовать это с изменяемой ссылкой, вы могли бы временно сохранить фиктивное значение там:
use std::mem;
fn increment_x_inline(x: &mut X) {
let old = mem::replace(x, X::X1(String::new()));
*x = increment_x(old);
}
Создание пустой String
не так уж плохо (это всего лишь несколько указателей, нет выделения кучи), но это не всегда возможно. В этом случае вы можете использовать Option
:
fn increment_x_inline(x: &mut Option<X>) {
let old = x.take();
*x = old.map(increment_x);
}
Смотрите также:
Ответ 2
Если вы хотите сделать это, не выходя из значения без затрат, вам придется прибегнуть к небезопасному коду (AFAIK):
use std::mem;
#[derive(Debug)]
enum X {
X1(String),
X2(String),
}
fn increment_x(x: &mut X) {
let interim = unsafe { mem::uninitialized() };
let prev = mem::replace(x, interim);
let next = match prev {
X::X1(s) => X::X2(s),
X::X2(s) => X::X1(s),
};
let interim = mem::replace(x, next);
mem::forget(interim); // Important! interim was never initialized
}