Change the enumeration option when moving the field to the new variant

I want to update an enumerated variant when moving the field of the old variant to the new one without cloning:

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), } } 

This does not work because we cannot move s from &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 

Please do not offer such things as the implementation of enum X { X1, X2 } and the use of struct S { variant: X, str: String } , etc. This is a simplified example. Imagine that there are many other fields in the variants and you want to move one field from one variant to another.

+11
source share
2 answers

This does not work because we cannot move s from &mut X

Then do not do this ... take the structure by value and return a new one:

 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), } } 

Ultimately, the compiler protects you, because if you could infer a string from an enumeration, it would be in some kind of semi-created state. Who will be responsible for freeing the line if the function panics at this moment? Should it free a line in enum or a line in a local variable? It cannot be both, since double freeing is a memory security issue.

If you had to implement this with a mutable reference, you could temporarily save the dummy value there:

 use std::mem; fn increment_x_inline(x: &mut X) { let old = mem::replace(x, X::X1(String::new())); *x = increment_x(old); } 

Creating an empty String not so bad (these are just a few pointers, no heap allocation), but this is not always possible. In this case, you can use Option :

 fn increment_x_inline(x: &mut Option<X>) { let old = x.take(); *x = old.map(increment_x); } 

See also:

+11
source

If you want to do this without leaving the value without cost, you will have to resort to unsafe code (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 } 
+4
source

All Articles