99

I have an enum with many values and I'd like to write the name of one of its values to a stream:

enum Foo {
    Bar = 0x00,
    Baz = 0x01,
    Qux = 0x02,
    // ...
    Quux = 0xFF,
}

I can derive Debug and do

writer.write(format!("I am {:?}", Foo::Quux).as_bytes())

which will output e.g. I am Quux. That's fine, except that

  • I want to do this for user-facing output, so Debug isn't appropriate
  • It would be very helpful to get the enum as a string (rather than writing directly to a stream), because then I can incorporate its length into some wonky formatting calculations I want to do.
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Iskar Jarak
  • 5,136
  • 4
  • 38
  • 60

3 Answers3

134

Probably the easiest way would be to implement Display by calling into Debug:

impl fmt::Display for Foo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
        // or, alternatively:
        // fmt::Debug::fmt(self, f)
    }
}

Then you can use to_string() to get a String representation:

let s: String = Foo::Quux.to_string();

If you have many enums which you want to print, you can write a trivial macro to generate the above implementation of Display for each of them.

Unfortunately, in Rust reflective programming is somewhat difficult. There is no standard way, for example, to get a list of all variants of a C-like enum. Almost always you have to abstract the boilerplate with custom-written macros (or find something on crates.io). Maybe this will change in the future if someone would write an RFC and it would get accepted.

LeoDog896
  • 3,472
  • 1
  • 15
  • 40
Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • 18
    If you `use std::fmt::{self, Debug, Display}`, just calling `Debug::fmt(self, f)` is simpler. – Veedrac Sep 22 '15 at 14:05
  • Unfortunately this seems to put quotation marks around the output. – David C. Bishop Feb 13 '22 at 21:24
  • 1
    @DavidC.Bishop [No, it doesn't.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d6aef09223299d9692276c8e43677c5d) Of course, it will if you call Debug on the resulting string. – leo848 Dec 26 '22 at 23:21
  • Even with @Veedrac's include, I'm getting this error: `use of undeclared crate or module `fmt``. Does this answer need to be updated for Rust 2021? – Raleigh L. Jul 11 '23 at 05:49
  • @RaleighL. It's been a while since I've touched Rust but I seriously doubt they've lost `std::fmt`; probably your imports are just bad somehow. – Veedrac Jul 11 '23 at 13:35
  • @RaleighL. you must have `use std::fmt` before writing `impl fmt::Display ...`, I think this is the most likely reason for your error. – Vladimir Matveev Aug 18 '23 at 22:30
56

Since the names of enum variants are fixed, you don't need to allocate a String, a &'static str will suffice. A macro can remove the boilerplate:

macro_rules! enum_str {
    (enum $name:ident {
        $($variant:ident = $val:expr),*,
    }) => {
        enum $name {
            $($variant = $val),*
        }

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

enum_str! {
    enum Foo {
        Bar = 0x00,
        Baz = 0x01,
        Qux = 0x02,
        //...
        Quux = 0xFF,
    }
}

fn main() {
    assert_eq!(Foo::Baz.name(), "Baz");
}

Even better, you can derive these with a crate like strum_macros.

In strum 0.10, you can use AsStaticRef / AsStaticStr to do the exact same code:

extern crate strum; // 0.10.0
#[macro_use]
extern crate strum_macros; // 0.10.0

use strum::AsStaticRef;

#[derive(AsStaticStr)]
enum Foo {
    Bar = 0x00,
    Baz = 0x01,
    Qux = 0x02,
    //...
    Quux = 0xFF,
}

fn main() {
    assert_eq!(Foo::Baz.as_static(), "Baz");
}

In strum 0.9, the string slice's lifetime is not 'static in this case:

#[macro_use]
extern crate strum_macros; // 0.9.0

#[derive(AsRefStr)]
enum Foo {
    Bar = 0x00,
    Baz = 0x01,
    Qux = 0x02,
    //...
    Quux = 0xFF,
}

fn main() {
    assert_eq!(Foo::Baz.as_ref(), "Baz");
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • This is the best one I have found. Now, there is another correction that can be done to make macro support both `Bar = 0x00` and `Bar` enum types. One with default and other without it. You can change `$($variant:ident = $val:expr),*,` --> `$($variant:ident = $($val:expr)?),*,` and `$($variant = $val),*` --> `$($variant:ident = $val:expr),*,`. Now it is perfect! – Himujjal Aug 12 '20 at 18:12
  • Perhaps you even can use a `const fn` for `fn name()`? – nalply Oct 26 '20 at 10:07
  • @nalply yes, that should be possible in modern Rust versions. – Shepmaster Oct 26 '20 at 14:55
  • With the firs solution, what if you wanted to use ```#[derive(Debug, Clone)]``` on ```enum Foo```? ```enum_str!{#[derive(Debug, Clone)] enum Foo{Bar}}``` causes ```no rules expected this token in macro call``` – ANimator120 Apr 16 '21 at 21:05
  • @ANimator120 you'd follow the steps outlined in [Generating documentation in macros](https://stackoverflow.com/a/33999625/155423), which demonstrates how to handle the `meta` from the `derive`. – Shepmaster Apr 22 '21 at 17:38
-3
#[derive(Debug)]
enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

pub fn enums_for_vector() {

    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];

    for i in &row {
        let v = match i {
            SpreadsheetCell::Float(n) => n.to_string(),
            SpreadsheetCell::Int(i) => i.to_string(),
            SpreadsheetCell::Text(s) => s.to_string(),
        };
        println!("{}", v);
    }
}
lekan
  • 1