Hi,
I have a enum which has to update it self based on it's current value.
Since this is not possible with match &mut e ... without unsafe,
I used a self consuming interface fn update(self)->Self. Now I am unsure how to integrate this in the rest of my system. Should i make all structs with the enum as member self consuming or just use std::mem::replace.
My understanding is that both have performance drawbacks depending on the compilers whim.
Using a self consuming method seems more natural since rust is move by default.
Is there some better way i should consider(I want to avoid unsafe) or is one superior.
Here is a small example to make my question clearer:
enum E{
A(f32),
B(i32),
}
impl E {
fn update(self)->Self{
match self{
E::A(v) => E::B(v as i32),
E::B(v) => E::A(v as f32)
}
}
}
impl Default for E{
fn default() -> Self {
E::A(0.0)
}
}
struct SomeVeryLargeNestedStruct{
e:E,
}
impl SomeVeryLargeNestedStruct{
fn update1(self)->Self{
Self{
e:self.e.update()
}
}
fn update2(&mut self){
self.e = std::mem::replace(&mut self.e,Default::default()).update();
}
}
fn main() {
let mut s = SomeVeryLargeNestedStruct{ e:Default::default()};
//option 1
s = s.update1();
//option 2
s.update2();
}
It's perfectly possible to match on a mutable reference without unsafe. What makes you think it isn't? Perhaps we can show you an improvement to the code you tried and didn't work.
There's no need to clone. You can mem::take the vector out of the mutable reference:
use std::mem;
impl E {
fn update(&mut self) {
*self = match *self {
E::A(ref mut v) => E::B(mem::take(v)),
E::B(ref mut v) => E::A(mem::take(v)),
};
}
}
This safely replaces the vector with an empty one (which does not allocate), returning the original by-value.
By the way, if all your associated values are of the same type, just pull the vector out in a struct and make the enum a pure discriminant (which can be Copy and which will be another field in the struct). That design will be much easier to work with.
This case can be solved by using either mem::take on the non-Copy fields (provided that all those fields have some type with cheaply-constructible default values like Vec does), or by using mem::replace on the whole enum (provided that at least one enum variant has no fields or cheaply-constructible fields). Or, if none of these alternatices are possible, you can use a helper function like the ones provided in the crate replace_with.
(As I was writing this, @H2CO3 provided some code using the abovementioned option of mem::takeing individual fields)
The construction of an empty vector is trivial, it does not allocate, it is implemented by creating a dangling but correctly aligned pointer. It will be optimized out completely.
I think relying on a separate dependency just for this trivial operation is much more "unnecessary".