Существует ли более программный подход к реализации трейта Display для перечислений?

Вопрос или проблема

В настоящее время я реализую трейты Display для перечислений следующим образом:

#[derive(Debug)]
enum Thing {
    SomeThing,
    AnotherThing,
}

impl Display for Thing {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match *self {
            Thing::SomeThing => write!(f, "SOME_THING"),
            Thing::AnotherThing => write!(f, "ANOTHER_THING"),
        }
    }
}

Это кажется утомительным и повторяющимся. Есть ли другой способ? Учитывая, что существует предсказуемый шаблон.

Я попробовал следующее, пока не осознал очевидную ошибку в этом подходе:

impl Display for TokenType {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let thing = self.to_string();
        let thing_split: Vec<&str> = thing.split("/(?=[A-Z])/").collect();
        write!(f, "{}_{}", thing_split[0].to_uppercase(), thing_split[1].to_uppercase())
    }
}

Метод to_string становится доступным после реализации трейта Display:

Этот трейт автоматически реализуется для любого типа, который реализует трейт Display. Таким образом, ToString не следует реализовывать напрямую: вместо этого следует реализовать Display, и вы получите реализацию ToString бесплатно.

Поскольку вы всё равно используете Debug, вы можете использовать его:

impl Display for Thing {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result  {
         write!(f,"{:?}",*self)
    }
}

Ответ или решение

Программный подход к реализации трейта Display для перечислений

При реализации трейта Display для перечислений в Rust часто возникает необходимость избегать повторений и упрощать код. В этом ответе я предложу несколько вариантов, позволяющих минимизировать трудоемкость процесса, особенно если структура имен в перечислении следует предсказуемой модели.

Текущая реализация

Ваша текущая реализация трейта Display выглядит следующим образом:

#[derive(Debug)]
enum Thing {
    SomeThing,
    AnotherThing,
}

impl Display for Thing {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match *self {
            Thing::SomeThing => write!(f, "SOME_THING"),
            Thing::AnotherThing => write!(f, "ANOTHER_THING"),
        }
    }
}

Этот подход, хотя и работает, может быть довольно громоздким, особенно при добавлении новых вариантов перечисления.

Более программные подходы

  1. Использование Debug для вывода

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

    impl Display for Thing {
       fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result  {
           write!(f, "{:?}", *self)
       }
    }

    Данный подход имеет свои ограничения, так как вывод может не соответствовать вашим требованиям по форматированию.

  2. Создание макроса для генерации кода

    Если ваше перечисление содержит множество вариантов, вы можете рассмотреть возможность использования макросов для автоматической генерации кода Display. Например:

    macro_rules! impl_display_for_enum {
       ($enum_name:ident, $($variant:ident),+) => {
           impl Display for $enum_name {
               fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
                   let name = match self {
                       $( $enum_name::$variant => stringify!($variant), )+
                   };
                   write!(f, "{}", name.to_uppercase().replace('_', ""))
               }
           }
       };
    }
    
    #[derive(Debug)]
    enum Thing {
       SomeThing,
       AnotherThing,
    }
    
    impl_display_for_enum!(Thing, SomeThing, AnotherThing);

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

  3. Использование ToString для простоты

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

    impl Thing {
       fn to_custom_string(&self) -> String {
           match self {
               Thing::SomeThing => "SOME_THING".to_string(),
               Thing::AnotherThing => "ANOTHER_THING".to_string(),
           }
       }
    }
    
    impl Display for Thing {
       fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
           write!(f, "{}", self.to_custom_string())
       }
    }

Оба подхода обеспечивают более программный и менее повторяемый способ реализации трейта Display. Каждый метод имеет свои нюансы, и выбор подхода зависит от конкретных требований вашего проекта.

Заключение

При реализации трейта Display для перечислений в Rust возможно значительно упростить код с помощью макросов или применения трейта Debug. Эти методы способствуют повышению читабельности и поддерживаемости кода. Не забывайте о требованиях вашего проекта и специфических форматах вывода при выборе подхода.

Оцените материал
Добавить комментарий

Капча загружается...