Что такое идиоматический c способ применения функции к вариантам перечисления, которые могут изменить тип варианта? - PullRequest
1 голос
/ 03 августа 2020

У меня есть перечисление с двумя вариантами, и у меня есть функция, которая может изменить активный вариант перечисления. Я также хотел бы разделить свой код между двумя вариантами перечисления, но это создает проблему, заключающуюся в том, что функция типа варианта не может принимать изменяемую ссылку на себя, поскольку она не может изменить вариант перечисления, в которое оно не входит. . Если функция становится владельцем, я должен использовать фиктивную переменную, которая подходит для моего реального приложения, но не в целом.

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!");
}

Все это кажется немного утомительным, поэтому я подозреваю, что Я пропустил более простой способ сделать это.

1 Ответ

1 голос
/ 03 августа 2020

Если применение функции могло изменить вариант перечисления, то вообще нет смысла реализовывать ее на уровне варианта перечисления. Я бы просто реализовал его на уровне перечисления, например:


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

Если вы действительно хотите, чтобы эта функция была доступна на уровне варианта, то, по крайней мере, в случае Natural::dec, она не будет всегда быть успешным. Следовательно, функция должна возвращать что-то, чтобы указать случай, когда невозможно уменьшить его и по-прежнему оставаться натуральным числом.

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

Площадка

...