enums rust

enums - ¿Cómo asegurar que cada variante enum pueda ser devuelta desde una función específica en tiempo de compilación?



rust (2)

Tengo una enumeración:

enum Operation { Add, Subtract, } impl Operation { fn from(s: &str) -> Result<Self, &str> { match s { "+" => Ok(Self::Add), "-" => Ok(Self::Subtract), _ => Err("Invalid operation"), } } }

Quiero asegurarme en tiempo de compilación de que cada variante enum se maneja en la función from .

¿Por qué necesito esto? Por ejemplo, podría agregar una operación de Product y olvidarme de manejar este caso en la función from :

enum Operation { // ... Product, } impl Operation { fn from(s: &str) -> Result<Self, &str> { // No changes, I forgot to add a match arm for `Product`, "*". } }

¿Es posible garantizar que la expresión de coincidencia devuelva cada variante de una enumeración? Si no, ¿cuál es la mejor manera de imitar este comportamiento?


Si bien existe una forma complicada, y frágil, de inspeccionar su código con macros de procedimiento, una ruta mucho mejor es utilizar pruebas. Las pruebas son más robustas, mucho más rápidas de escribir y verificarán las circunstancias en las que se devuelve cada variante, no solo que aparece en algún lugar.

Si le preocupa que las pruebas continúen pasando después de agregar nuevas variantes a la enumeración, puede usar una macro para asegurarse de que se prueben todos los casos:

#[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, } }


Una solución sería generar toda la enumeración, variantes y brazos de traducción con una 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: "-" }

De esta manera, agregar una variante es trivial y no puede olvidar un análisis. También es una solución muy SECA.

Es fácil extender esta construcción con otras funciones (por ejemplo, la traducción inversa) que seguramente necesitará más adelante y no tendrá que duplicar el análisis de caracteres.

playground