Как обеспечить возможность возврата каждого варианта перечисления из конкретной функции во время компиляции?
У меня есть перечисление:
enum Operation {
Add,
Subtract,
}
impl Operation {
fn from(s: &str) -> Result<Self, &str> {
match s {
"+" => Ok(Self::Add),
"-" => Ok(Self::Subtract),
_ => Err("Invalid operation"),
}
}
}
Я хочу убедиться, что во время компиляции каждый вариант перечисления обрабатывается в from
функция.
Зачем мне это нужно? Например, я мог бы добавитьProduct
операции и забудьте обработать этот случай в from
функция:
enum Operation {
// ...
Product,
}
impl Operation {
fn from(s: &str) -> Result<Self, &str> {
// No changes, I forgot to add a match arm for `Product`.
match s {
"+" => Ok(Self::Add),
"-" => Ok(Self::Subtract),
_ => Err("Invalid operation"),
}
}
}
Можно ли гарантировать, что выражение соответствия возвращает все варианты перечисления? Если нет, как лучше всего имитировать такое поведение?
2 ответа
Решением было бы сгенерировать все перечисление, варианты и переводы с помощью макроса:
macro_rules! operations {
(
$($name:ident: $chr:expr)*
) => {
#[derive(Debug)]
pub enum Operation {
$($name,)*
}
impl Operation {
fn from(s: &str) -> Result<Self, &str> {
match s {
$($chr => Ok(Self::$name),)*
_ => Err("Invalid operation"),
}
}
}
}
}
operations! {
Add: "+"
Subtract: "-"
}
Таким образом, добавление варианта тривиально, и вы не можете забыть о синтаксическом анализе. Это также очень СУХОЕ решение.
Эту конструкцию легко расширить другими функциями (например, обратным переводом), которые вам наверняка понадобятся позже, и вам не придется дублировать синтаксический анализатор.
Хотя, безусловно, существует сложный и хрупкий способ проверки вашего кода с помощью процедурных макросов, гораздо лучше использовать тесты. Тесты более надежны, их гораздо быстрее писать, и они проверяют обстоятельства, при которых возвращается каждый вариант, а не только то, что он где-то появляется.
Если вас беспокоит, что тесты могут продолжаться после добавления новых вариантов в перечисление, вы можете использовать макрос, чтобы убедиться, что все случаи проверены:
#[derive(PartialEq, Debug)]
enum Operation {
Add,
Subtract,
}
impl Operation {
fn from(s: &str) -> Result<Self, &str> {
match s {
"+" => Ok(Self::Add),
"-" => Ok(Self::Subtract),
_ => Err("Invalid operation"),
}
}
}
macro_rules! ensure_mapping {
($($str: literal => $variant: path),+ $(,)?) => {
// assert that the given strings produce the expected variants
$(assert_eq!(Operation::from($str), Ok($variant));)+
// this generated fn will never be called but will produce a
// non-exhaustive pattern error if you've missed a variant
fn check_all_covered(op: Operation) {
match op {
$($variant => {})+
};
}
}
}
#[test]
fn all_variants_are_returned_by_from() {
ensure_mapping! {
"+" => Operation::Add,
"-" => Operation::Subtract,
}
}