7

I have an enum:

pub enum BoxColour {
    Red,
    Blue,
}

I not only want to get this value as a string, but I want the value to be converted to lower case.

This works:

impl Display for BoxColour {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.write_str(match self {
            BoxColour::Red => "red",
            BoxColour::Blue => "blue",
        })?;
        Ok(())
    }
}

When the list of colours grows, this list would need to be updated.

If I use the write! macro, it does not seem possible to manipulate the result because write! returns an instance of () instead of a String:

impl Display for BoxColour {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "{:?}", self)
    }
}

This suggests that this is working through side effects and maybe we could hack the same place in memory the value is going, but even if that is possible, it probably isn't a good idea...

11
  • 1
    write! returns an instance of () — it does not. It returns a fmt::Result, as shown by the return type of fmt. Commented Sep 1, 2021 at 14:06
  • 1
    ...and fmt::Result is a type for Result<(), fmt::Error>. Commented Sep 1, 2021 at 14:07
  • You have already answered the question you have asked ("How can I Display an enum in lowercase?"). Please clarify exactly what it is you are asking beyond that. Commented Sep 1, 2021 at 14:07
  • 1
    The question you've linked to has an answer using strum, and strum allows you to customize what the Display format will be. It seems like you've linked to a suitable solution. Commented Sep 1, 2021 at 14:09
  • @Shepmaster, thanks for the response. @Smitop correctly understood my intention (below). Yes, I meant to indicate the success path for Result is () instead of String or something I might know how to work with. I saw strum, but couldn't get that to work either, but that's a question for a different day. Commented Sep 1, 2021 at 14:24

4 Answers 4

12

The strum crate provides a derive macro for implementing Display for an enum, and optionally lowercasing the variant names:

use strum_macros::Display;

#[derive(Display)]
// If we don't care about inner capitals, we don't need to set `serialize_all` 
// and can leave parenthesis empty.
#[strum(serialize_all = "snake_case")]
pub enum BoxColour {
    Red,
    Blue,
    LightGreen,  // example of how inner capitals are treated
}

fn main() {
    for c in [BoxColour::Red, BoxColor::Blue, BoxColor::LightGreen] {
        println!("{}", c);
    }
}

You will also need the corresponding dependency on strum in your Cargo.toml:

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

This should print:

red
blue
light_green

strum will generate code similar to the match with BoxColour::Red => "red", cases that you mentioned, but without the need to update it manually.

Sign up to request clarification or add additional context in comments.

2 Comments

Kevin Reid, Cheers for response. It didn't work "as-is", but I've fixed it. :-) Some might quibble about adding the dependency, but admittedly this is a bit more elegant than @Smitop's solution. I'm not sure whether Stackoverflow allows me to change the accepted answer, but I'll try. :-)
@BrianKessler you can change anytime you like accepted answer is just "that what I choice to fix my problem" hint for future reader, don't worry about that.
8

Here is a way to do that without needing to manually update Display::fmt every time you add a new colour by using the derived Debug implementation and lowercasing that:

#[derive(Debug)]
pub enum BoxColour {
    Red,
    Blue,
}

impl fmt::Display for BoxColour {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "{}", format!("{:?}", self).to_lowercase())
    }
}

(playground)

Note that write! returns a Result<(), fmt::Error> (which is a fmt::Result), not a raw ().

But consider if it would just be better to manually update the list, or use a macro to specify that. Also consider how colours with multiple words (such as LightBlue) should be lowercased: is lightblue what you want there?

2 Comments

you format the argument you send to a formater ? that feel hacky and bad
@Stargateur the code does seem hacky, but the nested format is the only way to make the Debug output lowercase: there isn't any format specifier for general lowercasing (LowerHex/LowerExp exists but are only applicable to formatting numbers).
3

As an extension to @Smitop's answer, it is possible to generally display any value in lowercase by using a custom formatter, as such:

use std::fmt::{self, Write};

struct LowercaseFormatter<'a, 'b>(pub &'a mut fmt::Formatter<'b>);

impl<'a, 'b> fmt::Write for LowercaseFormatter<'a, 'b> {
    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
        for ch in s.chars() {
            self.0.write_fmt(format_args!("{}", ch.to_lowercase()))?;
        }
        
        Ok(())
    }
}

Then use it as such:

#[derive(Debug)]
pub enum BoxColour {
    Red,
    Blue,
}

impl fmt::Display for BoxColour {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(LowercaseFormatter(formatter), "{:?}", self)
    }
}

fn main() {
    println!("{}", BoxColour::Red); // "red"
}

Playground

This version doesn't allocate a string on every formatting, but it is also not very optimized, calling write_fmt for every character is relatively costly.

One alternative for just ascii characters, is to call write_char(ch.to_ascii_lowercase()), but writing a string a character at a time is also relatively costly.

A proper solution would partition the string in some way to be able to write all already lowercase characters all at once, and only special case the uppercase ones.

Comments

0

Or simply use serde and set the rename_all attribute to lowercase like this:

#[derive(Serialize)]
#[serde(rename_all = "lowercase")]
enum BoxColour {
    Red
    Blue
}

1 Comment

You forgot the #[derive(Serialize)]. Also, serde is much heavier than strum, and requires a serializer crate as well. So, unless you already use it, better use strum.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.