Есть ли в 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 => ...
}
Есть ли способ достичь той же цели? Я думаю, должен быть способ сделать это с помощью макроса, но я стараюсь избегать макросов, насколько это возможно, поскольку они делают код менее явным.
Хотя это более удобный синтаксис, чем if/else, он не решает проблему проверки полноты.
Вы можете сделать макрос:
#[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
Я ожидал, что это можно сделать без макросов, но, думаю, тогда это невозможно.
Если все ваши группы различны, вы можете добавить еще один уровень перечислений:
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(_))
}
}
Ищете спичечные гурды
x if x.is_prime() => {}
?