我有一个枚举:
enum Operation {
Add,
Subtract,
}
impl Operation {
fn from(s: &str) -> Result<Self, &str> {
match s {
"+" => Ok(Self::Add),
"-" => Ok(Self::Subtract),
_ => Err("Invalid operation"),
}
}
}
Run Code Online (Sandbox Code Playgroud)
我想确保在编译时在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"),
}
}
}
Run Code Online (Sandbox Code Playgroud)
是否可以保证match表达式返回枚举的每个变体?如果没有,模仿此行为的最佳方法是什么?
Den*_*ret 13
A solution would be to generate the whole enumeration, variants and translation arms with a macro:
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: "-"
}
Run Code Online (Sandbox Code Playgroud)
This way adding a variant is trivial and you can't forget a parsing. It's also a very DRY solution.
很容易用其他函数(例如,反向翻译)扩展此构造,您以后肯定会需要这些函数,而不必重复分析char。
Pet*_*all 12
While there is certainly a complicated — and fragile — way to inspect your code with procedural macros, a much better path is to use tests. Tests are more robust, much faster to write, and will verify the circumstances in which each variant is returned, not just that it appears somewhere.
If you are concerned that the tests might continue to pass after you add new variants to the enum, you can use a macro to ensure that all cases are tested:
#[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,
}
}
Run Code Online (Sandbox Code Playgroud)