4 votes

Voie canonique de traiter les énumérations avec différentes représentations en rouille

Je utilise une enum pour chaque continent dans mon programme Rust. Asie, Europe, etc. Cependant, cette enum correspond à un entier dans la définition de la base de données, elle peut être construite à partir d'une source externe et peut être transformée en un autre "type" si nécessaire.

Par exemple

enum ContinentKind {
    Asia,
    Europe,
    Africa,
    America,
    Oceania,
}

impl ContinentKind {
    fn new(external_input: &str) -> Result {
        match external_input {
            "ASIA" => Ok(ContinentKind::Asia),
            "EUROPE" => Ok(ContinentKind::Europe),
            "AFRICA" => Ok(ContinentKind::Africa),
            "AMERICA" => Ok(ContinentKind::America),
            "OCEANIA" => Ok(ContinentKind::Oceania),
            _ => Err("Mauvaise entrée externe".to_string()),
        }
    }

    fn id(&self) -> u32 {
        match *self {
            ContinentKind::Asia => 1,
            ContinentKind::Europe => 2,
            ContinentKind::Africa => 3,
            ContinentKind::America => 4,
            ContinentKind::Oceania => 5,
        }
    }

    fn api_string(&self) -> String {
        match *self {
            ContinentKind::Asia => String::from("J'aime l'Asie"),
            ContinentKind::Europe => String::from("Je voyage en Europe"),
            ContinentKind::Africa => String::from("Bonjour l'Afrique"),
            ContinentKind::America => String::from("Amérique du Nord et du Sud"),
            ContinentKind::Oceania => String::from("O C E A N I E"),
        }
    }
}

Je peux donc utiliser ContinentKind::Asia la plupart du temps, mais j'utilise la méthode id(&self) pour obtenir un entier et sauvegarder dans la base de données, ou api_string(&self) pour retourner une chaîne dans mon serveur http.

Chaque continent a un type, un entier, une définition externe et une chaîne de description.

Asie, "ASIE", 1, "J'aime l'Asie"
Europe, "EUROPE", 2 "Je voyage en Europe"
Afrique, "AFRIQUE", 3, "Bonjour l'Afrique"
Amérique, "AMERICA", 4, "Amérique du Nord et du Sud"
Océanie "OCEANIE", 5, "O C E A N I E"

À première vue, le code fonctionne lorsque je l'appelle, mais je me demandais s'il existe un meilleur moyen de le faire, qui prend moins de code.

let my_type = ContinentKind::America;
println!("{}", my_type.id());
println!("{}", my_type.api_string());

let another_type = ContinentKind::new("AFRICA");

match another_type {
    Ok(v)=> println!("{}", v.id()),
    _ => println!("une erreur s'est produite"),
}

Et ce que je veux dire par là, c'est de finalement créer une structure comme celle-ci:

struct Continent {
    id: u32,
    kind: ContinentKind,
    external_str : String,
    internal_str: String,
}

qui a 2 constructeurs, from_id ou from_external_str et a le type imbriqué.

Par exemple:

#[derive(Debug)]
struct Continent {
    id: u32,
    kind: ContinentKind,
    external_str : String,
    internal_str: String,
}

impl Continent {

    fn from_external_string(external_input: &str) -> Result {

        match external_input {

            "ASIA" => Ok(Continent{id: 1, kind:ContinentKind::Asia, external_str: String::from("ASIA"), internal_str:String::from("J'aime l'Asie")}),
            "EUROPE" => Ok(Continent{id: 2, kind:ContinentKind::Europe, external_str: String::from("EUROPE"), internal_str:String::from("Je voyage en Europe")}),
            "AFRICA" => Ok(Continent{id: 3, kind:ContinentKind::Africa, external_str: String::from("AFRICA"), internal_str:String::from("Bonjour l'Afrique")}),
            "AMERICA" => Ok(Continent{id: 4, kind:ContinentKind::America, external_str: String::from("AMERICA"), internal_str:String::from("Amérique du Nord et du Sud")}),
            "OCEANIA" => Ok(Continent{id: 5, kind:ContinentKind::Oceania, external_str: String::from("OCEANIA"), internal_str:String::from("O C E A N I E")}),
            _ => Err("Mauvaise entrée externe".to_string()),
        }
    }

    fn from_database_id(id: u32) -> Result {

        match id {

            1 => Ok(Continent{id: 1, kind:ContinentKind::Asia, external_str: String::from("ASIA"), internal_str:String::from("J'aime l'Asie")}),
            2 => Ok(Continent{id: 2, kind:ContinentKind::Europe, external_str: String::from("EUROPE"), internal_str:String::from("Je voyage en Europe")}),
            3 => Ok(Continent{id: 3, kind:ContinentKind::Africa, external_str: String::from("AFRICA"), internal_str:String::from("Bonjour l'Afrique")}),
            4 => Ok(Continent{id: 4, kind:ContinentKind::America, external_str: String::from("AMERICA"), internal_str:String::from("Amérique du Nord et du Sud")}),
            5 => Ok(Continent{id: 5, kind:ContinentKind::Oceania, external_str: String::from("OCEANIA"), internal_str:String::from("O C E A N I E")}),
            _ => Err("Mauvaise entrée externe".to_string()),
        }
    }
}

2voto

Michael Anderson Points 21181

Une façon de rendre cela un peu plus propre est d'utiliser lazy_static pour mettre vos valeurs dans des valeurs globales statiques. Le cœur de cela ressemble à ceci :

lazy_static! {
    static ref ASIA: Continent = Continent::new(1, ContinentKind::Asia, "ASIE", "J'aime l'Asie");
    static ref EUROPE: Continent = Continent::new(2, ContinentKind::Europe, "EUROPE", "Je voyage en Europe");
    // ...
}

Vous pouvez ensuite construire vos fonctions Continent::from_external_string et Continent::from_database_id pour renvoyer des références à celles-ci (ou des copies si vous avez vraiment besoin de valeurs plutôt que de références).

impl Continent {
    fn from_external_string(external_input: &str) -> Result<&'static Continent, String> {
        match external_input {
            "ASIE" => Ok(&*ASIE),
            "EUROPE" => Ok(&*EUROPE),
            // ...
            _ => Err("Mauvaise entrée externe".to_string()),
        }
    }

    fn from_database_id(id: u32) -> Result<&'static Continent, String> {
        match id {
            1 => Ok(&*ASIE),
            2 => Ok(&*EUROPE),
            // ...
            _ => Err("Mauvaise entrée externe".to_string()),
        }
    }
}

Et enfin, si vous en avez toujours besoin, vous pouvez connecter les fonctions de l'énumération pour utiliser celles-ci aussi :

impl ContinentKind {
    fn to_continent(&self) -> &'static Continent {
        match self {
            ContinentKind::Asie => &*ASIE,
            ContinentKind::Europe => &*EUROPE,
            // ...
        }
    }
    fn id(&self) -> u32 {
        self.to_continent().id
    }
    // ...
}

REMARQUE : En général, je n'aime pas utiliser les globaux pour gérer ce genre de choses, mais si vous voulez vraiment utiliser des énumérations codées en dur, cela minimise au moins les morceaux que vous devez garder synchronisés.

1voto

Pour les conversions numériques, vous pouvez spécifier ces éléments en ligne :

#[repr(u32)]
enum Continent {
  Asie = 1,
  Europe = 2,
  ...
}

Vous pouvez ensuite utiliser une crate comme num_enum pour dériver les implémentations From/TryFrom pour vous. De même, vous pourriez utiliser une crate comme strum pour dériver les conversions de chaînes.

Ils ont l'avantage par rapport aux structures que vous décrivez de ne pas occuper de mémoire supplémentaire en stockant des copies redondantes d'informations, au détriment de devoir effectuer des contrôles à l'exécution (qui ont un certain coût) au lieu de simples recherches de champs de structures.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X