如何以小写形式显示枚举?

How can I display an enum in lowercase?

我有一个枚举:

pub enum BoxColour {
    Red,
    Blue,
}

我不仅要,还想把值转成小写

这个有效:

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(())
    }
}

当颜色列表增加时,需要更新此列表。

如果我使用 write! 宏,似乎无法操纵结果,因为 write! returns 是 () 的实例而不是 String:

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

这表明这是通过副作用起作用的,也许我们可以破解内存中值所在的相同位置,但即使这是可能的,也可能不是一个好主意...

这是一种无需每次添加新颜色时手动更新 Display::fmt 的方法,方法是使用派生的 Debug 实现并将其小写:

#[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)

请注意 write! returns 一个 Result<(), fmt::Error> (这是一个 fmt::Result), 不是 原始 ().

但请考虑是手动更新列表更好,还是使用宏来指定。还要考虑如何将包含多个单词(例如 LightBlue)的颜色小写:lightblue 是您想要的吗?

strum crate 提供了一个派生宏,用于为枚举实现 Display,并可选择小写变体名称:

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);
    }
}

您还需要在 Cargo.toml:

中对 strum 的相应依赖
[dependencies]
strum = { version = "0.21", features = ["derive"] }

这应该打印:

red
blue
light_green

strum 将生成类似于 match 和您提到的 BoxColour::Red => "red", 情况的代码,但无需手动更新。

作为@Smitop 答案的扩展,通常可以使用自定义格式化程序以小写形式显示任何值,例如:

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(())
    }
}

然后这样使用它:

#[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

这个版本并没有为每个格式都分配一个字符串,但也不是很优化,每个字符都调用write_fmt比较昂贵。

ascii 个字符的一种替代方法是调用 write_char(ch.to_ascii_lowercase()),但一次将一个字符写入字符串的成本也相对较高。

一个合适的解决方案是以某种方式对字符串进行分区,以便能够一次写入所有已经小写的字符,并且只写入大写字符的特殊情况。