Rust中enum和String的互转
source link: https://note.qidong.name/2023/03/rust-enum-str/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Rust中enum和String的互转
2023-03-16 23:04:30 +08 字数:1144 标签: Rust
任何语言中,enum
和String
两个类型的互转,都是基本操作,
因为String
广泛地使用于各个有可能的人机接口和(跨语言或跨环境)机机接口。
Rust中的互转难题 ¶
假设我有个值,本质上是个字符串,但只能是几个固定值。
比方说,速度单位km/h
和m/s
。
为了能很好地表达这个事,用enum
是最合适的。
struct Sth {
unit: SpeedUnit
}
enum SpeedUnit {
Kmph,
Mps,
}
然而,SpeedUnit::Kmph
和km/h
之间,如何实现互相转换,则是个较大的问题。
而且,这里的键和值是难以保持一致的。
- Rust命名规范中要求
enum
对象采用大驼峰方式命名,值不一定满足。 - 有些字符串中包含非法字符,如这里的
/
。
自己实现 ¶
如果要自己动手实现from_str
和to_str
,当然是可行的。
use std::str::FromStr;
#[derive(PartialEq)]
pub enum SpeedUnit {
Kmph,
Mps,
}
impl FromStr for SpeedUnit {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let unit = match s {
"km/h" => SpeedUnit::Kmph,
"m/s" => SpeedUnit::Mps,
_ => return Err(s.to_string()),
};
Ok(unit)
}
}
impl ToString for SpeedUnit {
fn to_string(&self) -> String {
let ret = match self {
SpeedUnit::Kmph => "km/h",
SpeedUnit::Mps => "m/s",
};
ret.to_string()
}
}
但是,这种实现的代价很大。
"km/h"
和"m/s"
被写了两次。为了避免重复,还需要定义一组常量。- 如果值域扩展,比如加一个
"m/h"
,相关的三个代码块都需要配合修改。 - 如果有很多这种
enum
,这些形式相同的代码,需要被反复写很多遍。
要解决这种问题,在Rust中需要用宏。
宏 ¶
不写了,不太会。直接看strum_macros源码吧。
用strum_macros ¶
use strum_macros::{Display, EnumString};
#[derive(Display, EnumString, PartialEq)]
enum SpeedUnit {
#[strum(serialize = "km/h")]
Kmph,
#[strum(serialize = "m/s")]
Mps,
}
strum_macros的Display和EnumString,可以很好地满足这个互转要求。 基本上可以理解为,它利用Rust宏,把原先3个代码块才能实现的功能,集中到了1个代码块。
除了互转之外,strum_macros还支持其它偏门或常见的enum
需求,详见文档。
测试代码 ¶
上文展示的两种实现,都可以通过以下测试用例。 这证明它们在这些使用场景下是等价的。
#[cfg(test)]
mod tests {
use std::str::FromStr;
use rstest::rstest;
use super::*;
#[rstest]
#[case("km/h", SpeedUnit::Kmph)]
#[case("m/s", SpeedUnit::Mps)]
fn test_speed_unit_parse(#[case] input: &str, #[case] expected: SpeedUnit) {
let unit = SpeedUnit::from_str(input).unwrap();
assert!(unit == expected);
}
#[rstest]
#[case("kmh")]
#[case("ms")]
#[should_panic]
fn test_speed_unit_parse_fail(#[case] input: &str) {
SpeedUnit::from_str(input).unwrap();
}
#[rstest]
#[case(SpeedUnit::Kmph, "km/h")]
#[case(SpeedUnit::Mps, "m/s")]
fn test_speed_unit_unparse(#[case] unit: SpeedUnit, #[case] expected: &str) {
let value = unit.to_string();
assert_eq!(value, expected)
}
}
注意:两段实现中都加入了PartialEq
,是为了使用第一个用例中的unit == expected
。
如果实际使用中没有类似判断相等的需求,可以不加PartialEq
。
如果需要还使用assert_eq!
,则需要加Debug
。
附Cargo.toml ¶
[dependencies]
strum = "0.24"
strum_macros = "0.24"
[dev-dependencies]
rstest = "0.15"
其中,strum和strum_macros都是必须的,而且版本要一致。
而rstest,则是支持本文测试代码中使用的case
功能,可选添加。
另附Python的实现 ¶
from enum import Enum
class SpeedUnit(str, Enum):
KMPH = 'km/h'
MPS = 'm/s'
assert SpeedUnit('km/h') is SpeedUnit.KMPH
assert SpeedUnit.MPS.value == 'm/s'
显然,Python更简单。
但这是有语法糖支持的情况下。 Rust能通过宏,自由地进行元编程,潜在的表现力更大。
其它语言……
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK