канонический способ обработки перечислений с различными представлениями в ржавчине - PullRequest
4 голосов
/ 13 июля 2020

Я использую перечисление для каждого континента в моей программе на Rust. Азия, Европа и др. c. Однако это перечисление соответствует целому числу в определении базы данных, оно может быть построено из внешнего источника и может быть преобразовано в другой «тип» по запросу.

Например,

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

impl ContinentKind {
    fn new(external_input: &str) -> Result<ContinentKind, String> {
        match external_input {
            "ASIA" => Ok(ContinentKind::Asia),
            "EUROPE" => Ok(ContinentKind::Europe),
            "AFRICA" => Ok(ContinentKind::Africa),
            "AMERICA" => Ok(ContinentKind::America),
            "OCEANIA" => Ok(ContinentKind::Oceania),
            _ => Err("Wrong external input".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("I love Asia"),
            ContinentKind::Europe => String::from("I travel to Europe"),
            ContinentKind::Africa => String::from("Hello Africa"),
            ContinentKind::America => String::from("North and South America"),
            ContinentKind::Oceania => String::from("O C E A N I A"),
        }
    }
}

Итак Я могу использовать ContinentKind :: Asia большую часть времени, но я использую метод id (& self), чтобы взять целое число и сохранить его в базе данных, или api_string (& self), чтобы вернуть строку на моем http-сервере.

Каждый континент имеет тип, целое число, внешнее определение и строку описания.

Asia, "ASIA", 1, "I love Asia"
Europe, "EUROPE", 2 "I travel to Europe"
Africa, "AFRICA", 3, "Hello Africa"
America, "AMERICA", 4, "North and South America"
Oceania "OCEANIA", 5, "O C E A N I A"

На первый взгляд код работает, когда я звоню, но мне было интересно, есть ли лучший способ сделать это, требует меньше кода.

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!("an error happend"),
}

И я имею в виду, наконец, создать такую ​​структуру:

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

, которая имеет 2 конструкции, from_id или from_external_str и иметь вложенный тип.

Например:

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

impl Continent {

    fn from_external_string(external_input: &str) -> Result<Continent, String> {

        match external_input {

            "ASIA" => Ok(Continent{id: 1, kind:ContinentKind::Asia, external_str: String::from("ASIA"), internal_str:String::from("I love Asia")}),
            "EUROPE" => Ok(Continent{id: 2, kind:ContinentKind::Europe, external_str: String::from("EUROPE"), internal_str:String::from("I travel to Europe")}),
            "AFRICA" => Ok(Continent{id: 3, kind:ContinentKind::Africa, external_str: String::from("AFRICA"), internal_str:String::from("Hello Africa")}),
            "AMERICA" => Ok(Continent{id: 4, kind:ContinentKind::America, external_str: String::from("AMERICA"), internal_str:String::from("North and South America")}),
            "OCEANIA" => Ok(Continent{id: 5, kind:ContinentKind::Oceania, external_str: String::from("OCEANIA"), internal_str:String::from("O C E A N I A")}),
            _ => Err("Wrong external input".to_string()),
        }
    }

    fn from_database_id(id: u32) -> Result<Continent, String> {

        match id {

            1 => Ok(Continent{id: 1, kind:ContinentKind::Asia, external_str: String::from("ASIA"), internal_str:String::from("I love Asia")}),
            2 => Ok(Continent{id: 2, kind:ContinentKind::Europe, external_str: String::from("EUROPE"), internal_str:String::from("I travel to Europe")}),
            3 => Ok(Continent{id: 3, kind:ContinentKind::Africa, external_str: String::from("AFRICA"), internal_str:String::from("Hello Africa")}),
            4 => Ok(Continent{id: 4, kind:ContinentKind::America, external_str: String::from("AMERICA"), internal_str:String::from("North and South America")}),
            5 => Ok(Continent{id: 5, kind:ContinentKind::Oceania, external_str: String::from("OCEANIA"), internal_str:String::from("O C E A N I A")}),
            _ => Err("Wrong external input".to_string()),
        }
    }
}

Ответы [ 2 ]

2 голосов
/ 14 июля 2020

Один из способов немного очистить это - использовать lazy_static, чтобы поместить ваши значения в глобальные значения stati c. Ядро этого выглядит так:

lazy_static! {
    static ref ASIA: Continent = Continent::new(1, ContinentKind::Asia, "ASIA", "I love Asia");
    static ref EUROPE: Continent = Continent::new(2, ContinentKind::Europe, "EUROPE", "I travel to Europe");
    // ...
}

Затем вы можете создать свои Continent::from_external_string и Continent::from_database_id, чтобы возвращать ссылки на них (или копии, если вам действительно нужны значения, а не ссылки).

impl Continent {
    fn from_external_string(external_input: &str) -> Result<&'static Continent, String> {
        match external_input {
            "ASIA" => Ok(&*ASIA),
            "EUROPE" => Ok(&*EUROPE),
            // ...
            _ => Err("Wrong external input".to_string()),
        }
    }

    fn from_database_id(id: u32) -> Result<&'static Continent, String> {
        match id {
            1 => Ok(&*ASIA),
            2 => Ok(&*EUROPE),
            // ...
            _ => Err("Wrong external input".to_string()),
        }
    }
}

И, наконец, если они вам все еще нужны, вы можете подключить функции перечисления, чтобы использовать их тоже:

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

ПРИМЕЧАНИЕ. В общем, мне не нравится использовать global для управления этим. вроде того, но если вы действительно хотите использовать жестко запрограммированные перечисления, это, по крайней мере, минимизирует биты, которые вам нужно синхронизировать c.

1 голос
/ 14 июля 2020

Для числовых покрытий вы можете указать эти встроенные:

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

Затем вы можете использовать ящик, например num_enum , чтобы получить From / TryFrom реализации для вас. Точно так же вы можете использовать ящик, такой как strum , для получения преобразований строк.

Они имеют преимущество перед описанными вами структурами в том, что они не занимают дополнительную память для хранения избыточных копий информации в за счет необходимости выполнять сопоставления во время выполнения (что сопряжено с некоторыми накладными расходами) вместо простого поиска полей структуры.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...