Mut self vs &mut self for update method

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();
}


I'd write a &mut self version, such as

impl E {
    fn update(&mut self) {
        *self = match self {
            E::A(v) => E::B(*v as i32),
            E::B(v) => E::A(*v as f32)
        };
    }
}

There should be no need for unsafe to do what you want, although it may require storing a portion of the enum for later, such as.

2 Likes

Thanks, but wouldn't this require a clone/copy for the enum data, can that be avoided?

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.

Something like this

enum E{
    A(Vec<f32>),
    B(Vec<f32>),

}

impl E {
    fn update(&mut self) {
        *self = match self {
            E::A(v) => E::B(*v),
            E::B(v) => E::A(*v)
        };
    }
}

which would require a unnecessary clone

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.

2 Likes

isn't that the same as std::mem::replace with default?

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)

Yes it is, but it's a little bit less verbose. Is there anything wrong with it?

It just seems like unnecessary extra work to construct the default instance and then throw it away.

Thanks! The replace_with crate is exactly what I was looking for.

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".

3 Likes

You are probably right but i do enjoy my premature optimization's.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.