Как сгруппировать членов перечисления, но сохранить проверку полноты совпадений

Есть ли в Rust способ «группировать» члены перечисления таким образом, чтобы я мог получить функцию предиката И использовать этот предикат в операторе сопоставления?

Допустим, у меня есть это перечисление:

enum Number {
  One,
  Two,
  Three,
  Four,
  Five,
  Six
}

И этот предикат:

impl Number {
  fn is_prime(&self) -> bool {
    self == Number::Two || self == Number::Three || self == Number::Five
  }
}

Тогда моя проблема в том, что в моей кодовой базе есть несколько операторов сопоставления, которые обрабатывают вещи по-разному, если число простое:

match number {
  Number::Two | Number::Three | Number::Five => {/* do something for primes*/}
  Number::One => ...
  Number::Four => ...
  Number::Six => ... 
}

Я хотел бы иметь только один источник истины о том, что такое простое число. Конечно, я мог бы добавить проверку предиката перед каждым оператором сопоставления:

if number.is_prime() {
  /* do something for primes*/
} else {
  match number {
    Number::One => ...
    Number::Four => ...
    Number::Six => ...
    _ => {} // should never happen
  }
}

Но тогда я теряю проверку полноты. Мне нужно добавить всеобъемлющий рычаг, который не обеспечивает мне безопасность компиляции, чтобы гарантировать, что любой, кто позже добавляет член в Number, должен явно обрабатывать его в каждом операторе match.

В идеале я хотел бы сделать что-то вроде этого:

match number {
  is_prime() => {/* do something for primes*/}
  Number::One => ...
  Number::Four => ...
  Number::Six => ... 
}

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

Ищете спичечные гурды x if x.is_prime() => {}?

user459872 18.03.2024 14:51

Хотя это более удобный синтаксис, чем if/else, он не решает проблему проверки полноты.

Ricola 18.03.2024 15:53
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
2
81
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете сделать макрос:

#[macro_export]
macro_rules! primes {
    () => {
        $crate::Number::Two | $crate::Number::Three | $crate::Number::Five
    };
}

impl Number {
    pub fn factors(&self) -> u8 {
        use Number::*;
        match self {
            primes!() => 2,
            One => 1,
            Four => 3,
            Six => 4,
        }
    }
}

Этот макрос предполагает, что Number общедоступен в корне крейта. Используйте соответствующий путь, если он находится где-то еще. Я не думаю, что есть способ избежать записи всего пути для каждого варианта, поскольку в шаблоне не может быть операторов use, хотя, если он очень длинный, вы можете создать еще один макрос только для части пути. Подробнее о $crate здесь.

С другой стороны, это работает как часть более крупной модели:

primes!() | Number::One

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

Ricola 22.03.2024 16:06

Если все ваши группы различны, вы можете добавить еще один уровень перечислений:

enum Numbers {
    Prime(Prime),
    NonPrime(NonPrime),
}
enum Prime {
    Two,
    Three,
    Five,
}
enum NonPrime {
    One,
    Four,
    Six,
}

Затем вы можете сопоставить внешний слой:

use Number::*;
use Prime::*;
use NonPrime::*;
match number {
    Prime(_) => todo!("do something for primes"),
    NonPrime(One) => todo!("do something for one"),
    NonPrime(_) => todo!("do something for other non primes"),
}

is_prime тоже становится очень тривиальным:

impl Number {
    fn is_prime(self) -> bool {
        matches!(self, Number::Prime(_))
    }
}

Другие вопросы по теме