What is the idiomatic way of applying a function to enum variants that might change the type of variant?

Viewed 145

I have an enum with two variants and I have a function that might change the active variant of the enum. I would also like to split my code between the two enum variants, but this creates the challenge that the function of the variant type can't take an mutable reference to itself, since it cannot change the variant of an enum it's not a part of. If the function takes ownership, I have to use a dummy variable, which is fine for my actual application, but not in general.

struct Integer (i32);
struct Natural (u32);
enum Number {
    I(Integer),
    N(Natural),
}

impl Integer {
    // Decrement
    fn dec(&mut self) {
        self.0 -= 1;
    }
}

impl Natural {
    // Zero is a natural number that when decremented is not a natural number
    // therefore dec cannot take a &mut self
    fn dec(self) -> Number {
        if self.0 == 0 {
            Number::I(Integer(-1))
        } else {
            Number::N(Natural(self.0 - 1))
        }
    }
}

impl Number {
    // Is there a better way to do this?
    fn dec(&mut self) {
        let result = match self {
            Number::I(int) => {
                int.dec();
                return
            }
            Number::N(nat) => {
                // For this example making a dummy is possible
                // but what if it's not?
                let dummy = Natural(0);
                let extracted = std::mem::replace(nat, dummy);
                extracted.dec()
            }
        };
        *self = result;
    }
}

// Something like this would be nice
fn transform<T, F>(val: &mut T, f: F) where F : FnOnce(T) -> T {
    todo!("Magic!");
}

All of this feels a bit tedious, so I have a suspicion that I've missed some simpler way of doing this.

1 Answers

If applying a function could change the enum variant, then it doesn't make sense to implement it at all on the level of the enum variant. I would just implement it at the enum level, like this:


impl Number {
    
    fn dec(&mut self) {
        *self = match self {
            Number::I(i) => Number::I(Integer(i.0-1)),
            Number::N(n) if n.0==0 => Number::I(Integer(-1)),
            Number::N(n) => Number::N(Natural(n.0-1))
        }
    }
    
}

If you do want to have this function available at the variant level, then at least in the case of Natural::dec, it will not always be successful. Therefore the function needs to return something to indicate the case where it is not possible to decrement it and still remain a natural number.

impl Integer {
    fn dec(&mut self) {
        self.0 -= 1;
    }
}

struct NotNatural ();

impl Natural {
    fn dec(&mut self) -> Result<(), NotNatural> {
        if self.0 == 0 {
            Err(NotNatural())
        } else {
            self.0 -= 1;
            Ok(())
        }
    }
}

impl Number {
    
    fn dec(&mut self) {
        match self {
            Number::I(ref mut i) => i.dec(),
            Number::N(ref mut n) => match n.dec() {
                Ok(_) => (),
                Err(_) => {
                    *self = Number::I(Integer(-1));
                    ()
                }
            }
        }
    }
   
}

Playground

Related