如何将枚举显示为小写字母?

5

我有一个枚举:

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!返回的是()的实例,而不是 String :
impl Display for BoxColour {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "{:?}", self)
    }
}

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


1
“write!”不会返回()的实例,它返回一个fmt::Result,正如fmt的返回类型所示。 - Shepmaster
1
...ÞÇîfmt :: Resultµÿ»Result <(), fmt :: Error>þÜäþ▒╗Õ×ïÒÇé - smitop
1
你链接的问题(https://dev59.com/rlwY5IYBdhLWcg3wWmtn)有一个使用strum的答案,而[strum允许您自定义`Display`格式](https://docs.rs/strum_macros/0.21.1/strum_macros/derive.Display.html)。看起来你已经链接到了一个合适的解决方案。 - Shepmaster
@Shepmaster,感谢您的回复。@Smitop 正确理解了我的意图(如下)。是的,我的意思是要表明 Result 的成功路径是 () 而不是 String 或其他我可能知道如何处理的东西。我看到了 strum,但也无法让它工作,但这是另一天的问题。 - Brian Kessler
为什么第一个解决方案对你来说是个问题,以及你应该如何处理它。 - Stargateur
显示剩余6条评论
3个回答

10

strum crate提供了一个派生(derive)宏,用于实现枚举类型的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 会生成类似于您提到的带有 BoxColour::Red => "red"match 代码,但无需手动更新它。


1
Kevin Reid,感谢您的回复。它“原样”并没有起作用,但我已经修复了它。 :-) 有些人可能会对添加依赖项提出异议,但不可否认,这比@Smitop的解决方案更加优雅。我不确定Stackoverflow是否允许我更改接受的答案,但我会尝试。 :-) - Brian Kessler
2
@BrianKessler,你可以随时更改接受的答案,只需要说“这是我解决问题的选择”即可。给未来读者一个提示,不必担心这个问题。 - Stargateur

7
这里有一种方法可以在不需要手动更新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())
    }
}

(演示区)

请注意,write!返回一个Result<(), fmt::Error>(这是一个fmt::Result),而不是原始的()

但请考虑手动更新列表是否更好,或者使用宏来指定。另外请考虑多个单词组成的颜色(例如LightBlue)应该小写:那里你需要的是lightblue吗?


你会格式化发送给格式化程序的参数吗?那感觉很不专业和糟糕。 - Stargateur
@Stargateur,这段代码看起来有些hacky,但是嵌套格式是使调试输出小写的唯一方法:没有通用小写格式说明符(LowerHex/LowerExp存在,但仅适用于格式化数字)。 - smitop

4

作为对@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()),但逐个字符地写入字符串也相对较为昂贵。

一个正确的解决方案应该将字符串划分为某些部分,以便能够一次性写入所有已经转换成小写字母的字符,并且只特殊处理大写字母。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接