Avatar ·

Как получить строковое представление варианта перечисления (enum) в Rust?

Всем привет! Я только начал изучать Rust и столкнулся с проблемой, которая кажется простой, но я не могу найти элегантного решения. У меня есть перечисление для статусов заказа: ```rust enum OrderStatus { Pending, Processing, Shipped, Delivered, Cancelled, } ``` Мне нужно вывести текущий статус в консоль для логирования и для отображения пользователю. Я попробовал просто использовать `println!("{}", status)`, но компилятор выдаёт ошибку, что трейт `std::fmt::Display` не реализован для моего типа. Я знаю, что можно добавить `#[derive(Debug)]` и использовать `{:?}`, но это даёт вывод вроде `OrderStatus::Pending`, а мне нужно просто "Pending" без имени перечисления. Также пробовал писать match-выражение для каждого варианта, но это выглядит очень громоздко, особенно если вариантов много. Подскажите, как правильно и идиоматично получить строку из варианта enum в Rust? Желательно без лишнего копирования и с минимальным оверхедом. Заранее спасибо! <<>>

1. Реализация трейта Display — самый правильный способ

Самый идиоматичный и гибкий подход — реализовать трейт std::fmt::Display для вашего перечисления. Это позволяет контролировать вывод и использовать макросы println!, format! и write! напрямую.

use std::fmt;

enum OrderStatus {
    Pending,
    Processing,
    Shipped,
    Delivered,
    Cancelled,
}

impl fmt::Display for OrderStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            OrderStatus::Pending => write!(f, "Pending"),
            OrderStatus::Processing => write!(f, "Processing"),
            OrderStatus::Shipped => write!(f, "Shipped"),
            OrderStatus::Delivered => write!(f, "Delivered"),
            OrderStatus::Cancelled => write!(f, "Cancelled"),
        }
    }
}

fn main() {
    let status = OrderStatus::Shipped;
    println!("Статус заказа: {}", status);
    // Вывод: Статус заказа: Shipped
}

Преимущества:

  • Полный контроль над форматированием
  • Можно добавлять дополнительные данные (например, для вариантов с полями)
  • Работает со всеми макросами форматирования
  • Нулевые накладные расходы во время выполнения

2. Использование крейта strum — для ленивых

Если у вас много вариантов или вы часто конвертируете enum в строку, используйте крейт strum с макросом Display. Добавьте в Cargo.toml:

[dependencies]
strum = { version = "0.26", features = ["derive"] }

Теперь код становится однострочным:

use strum::Display;

#[derive(Display)]
enum OrderStatus {
    Pending,
    Processing,
    Shipped,
    Delivered,
    Cancelled,
}

fn main() {
    let status = OrderStatus::Delivered;
    println!("{}", status); // "Delivered"
}

Важно: По умолчанию strum::Display использует snake_case для имён. Если нужно сохранить точное имя варианта, используйте атрибут #[strum(serialize_all = "PascalCase")].

3. Метод as_ref() — только для C-подобных enum

Если ваше перечисление не содержит данных (как в примере), вы можете использовать трюк с приведением к строковому срезу через as_ref() и std::mem::discriminant. Но это сложнее и менее читаемо, поэтому я не рекомендую новичкам.

4. Когда использовать какой подход?

  • Display вручную — когда нужно разное форматирование для разных вариантов или есть вложенные данные.
  • strum — для простых enum без данных, когда лень писать match.
  • Debug — только для отладки, не для пользовательского вывода.

Совет: Всегда старайтесь реализовать Display для enum, которые будут выводиться пользователю. Это улучшает читаемость кода и делает его более идиоматичным для Rust.

<<>>

Альтернатива: функция as_str() и макрос для автоматической генерации

Если вы не хотите подключать внешние крейты, но хотите избежать ручного написания match, можно написать простой макрос для генерации метода as_str():

macro_rules! enum_to_str {
    ($name:ident { $($variant:ident),* $(,)? }) => {
        impl $name {
            fn as_str(&self) -> &'static str {
                match self {
                    $( $name::$variant => stringify!($variant), )*
                }
            }
        }
    };
}

enum OrderStatus {
    Pending,
    Processing,
    Shipped,
    Delivered,
    Cancelled,
}

enum_to_str!(OrderStatus { Pending, Processing, Shipped, Delivered, Cancelled });

fn main() {
    let s = OrderStatus::Shipped.as_str();
    println!("{}", s); // "Shipped"
}

Плюсы: Нет зависимостей, полный контроль, нулевой оверхед.

Минусы: Нужно поддерживать список вариантов в макросе вручную, не работает с enum, содержащими данные.

Для enum с данными лучше использовать Display или strum, так как макрос не сможет корректно обработать поля.