rust 中 string 与其它类型间的互转
2019-10-13 19:38 rust
Rust 中经常用到的字符串与其它数据类型之间的转换, 可以通过其它类型实现几个预定义的 Trait 来完成.
其中, 包括:
std::str::FromStr
, 用于从字符串转为指定的类型, 提供了parse()
方法std::string::ToString
, 用于将将指定的类型转为字符串, 提供了to_string()
方法
在标准库中, 已经实现了基本数据类型(比如bool, i32, u64, f64等)与字符串之间的互转, 举例:
let i = "42".parse::<i32>();
assert_eq!(i, Ok(42));
以及:
let s = 3.14.to_string();
assert_eq!("3.14", s);
下面以 struct Color
为例, 说明一下如何为自定义的类型实现以上两个 Traits.
#[derive(Clone, Copy, Debug)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
pub alpha: u8,
}
#[derive(Debug)]
pub enum ParseColorError {
InvalidFormatError,
OutOfRangeError,
}
impl Color {
fn new(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
Color{red, green, blue, alpha}
}
}
impl From<std::num::ParseIntError> for ParseColorError {
fn from(_error: std::num::ParseIntError) -> Self {
Self::InvalidFormatError
}
}
impl std::string::ToString for Color {
fn to_string(&self) -> String {
String::from(format!("rgba({}, {}, {}, {})", self.red, self.green, self.blue, self.alpha))
}
}
impl std::str::FromStr for Color {
type Err = ParseColorError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.replace(" ", "");
if s.len() < 4 {
return Err(ParseColorError::InvalidFormatError);
}
if &s[0..1] == "#" {
match s.len() {
9 => {
// #rrggbbaa
let red = u8::from_str_radix(&s[1..3], 16)?;
let green = u8::from_str_radix(&s[3..5], 16)?;
let blue = u8::from_str_radix(&s[5..7], 16)?;
let alpha = u8::from_str_radix(&s[7..9], 16)?;
return Ok(Color::new(red, green, blue, alpha));
},
7 => {
// #rrggbb
let red = u8::from_str_radix(&s[1..3], 16)?;
let green = u8::from_str_radix(&s[3..5], 16)?;
let blue = u8::from_str_radix(&s[5..7], 16)?;
let alpha = 100;
return Ok(Color::new(red, green, blue, alpha));
},
4 => {
// #rgb
let red = u8::from_str_radix(&s[1..2], 16)?;
let green = u8::from_str_radix(&s[2..3], 16)?;
let blue = u8::from_str_radix(&s[3..4], 16)?;
let alpha = 100;
// Duplicate bytes
let red = red * 17;
let green = green * 17;
let blue = blue * 17;
return Ok(Color::new(red, green, blue, alpha));
},
_ => {
return Err(ParseColorError::InvalidFormatError);
}
}
} else if &s[0..5] == "rgba(" && &s[s.len()-1..s.len()] == ")" {
// rgba(101, 255, 255, 100)
let parts: Vec<&str> = s[4..].trim_matches(|p| p == '(' || p == ')')
.split(',')
.collect();
if parts.len() != 4 {
return Err(ParseColorError::InvalidFormatError);
}
let red = parts[0].parse::<u8>()?;
let green = parts[1].parse::<u8>()?;
let blue = parts[2].parse::<u8>()?;
let alpha = parts[3].parse::<u8>()?;
return Ok(Color::new(red, green, blue, alpha));
} else if &s[0..4] == "rgb(" && &s[s.len()-1..s.len()] == ")" {
// rgb(101, 255, 255)
let parts: Vec<&str> = s[3..].trim_matches(|p| p == '(' || p == ')')
.split(',')
.collect();
if parts.len() != 3 {
return Err(ParseColorError::InvalidFormatError);
}
let red = parts[0].parse::<u8>()?;
let green = parts[1].parse::<u8>()?;
let blue = parts[2].parse::<u8>()?;
let alpha = 100;
return Ok(Color::new(red, green, blue, alpha));
}
return Err(ParseColorError::InvalidFormatError);
}
}
fn main() {
let color = Color::new(255, 255, 255, 100);
println!("color: {:?}", color);
println!("color: {}", color.to_string());
let colors = [
"#fea",
"#ffeeaa",
"#ffeeaa99",
"rgb ( 255, 255, 200)",
"rgba ( 255, 255, 200, 100)",
];
for color_str in &colors {
let color = color_str.parse::<Color>();
println!("color_str: {}, color: {:?}", color_str, color);
}
}
首先, 定义数据结构:
#[derive(Clone, Copy, Debug)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
pub alpha: u8,
}
颜色值中, 定义了四个颜色通道, red, green, blue 和 alpha.
这里的 alpha 通道的取值范围是 0 ~ 255
, 有些代码中使用 0 ~ 100
的范围.
之后, 就是简单定义一个构造函数:
impl Color {
fn new(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
Color{red, green, blue, alpha}
}
}
接下来, 先定义 ToString Trait
:
impl std::string::ToString for Color {
fn to_string(&self) -> String {
String::from(format!("rgba({}, {}, {}, {})", self.red, self.green, self.blue, self.alpha))
}
}
只是简单地将四个颜色通道都打印出来罢了, 比较简单.
更复杂的是, 如果从字符串里解析颜色值, 因为颜色可以用多种表示方法, 这里我们考虑支持的 是以下几种写法:
rgba(255, 255, 255, 255)
, 包含 alpha 通道的 10 进制写法rgb(255, 255, 255)
, 不包含 alpha 通道的 10 进制写法#ffeeaaff
, 包含 alpha 通道的 16 进制写法#ffeeaa
, 不包含 alpha 通道的 16 进制写法#fea
, 不包含 alpha 通道的 16 进制简写写法, 其实就相当于#ffeeaa
上面的逻辑, 实现起来也略微麻烦了些, 具体的代码是这样的:
impl std::str::FromStr for Color {
type Err = ParseColorError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.replace(" ", "");
if s.len() < 4 {
return Err(ParseColorError::InvalidFormatError);
}
if &s[0..1] == "#" {
match s.len() {
9 => {
// #rrggbbaa
let red = u8::from_str_radix(&s[1..3], 16)?;
let green = u8::from_str_radix(&s[3..5], 16)?;
let blue = u8::from_str_radix(&s[5..7], 16)?;
let alpha = u8::from_str_radix(&s[7..9], 16)?;
return Ok(Color::new(red, green, blue, alpha));
},
7 => {
// #rrggbb
let red = u8::from_str_radix(&s[1..3], 16)?;
let green = u8::from_str_radix(&s[3..5], 16)?;
let blue = u8::from_str_radix(&s[5..7], 16)?;
let alpha = 100;
return Ok(Color::new(red, green, blue, alpha));
},
4 => {
// #rgb
let red = u8::from_str_radix(&s[1..2], 16)?;
let green = u8::from_str_radix(&s[2..3], 16)?;
let blue = u8::from_str_radix(&s[3..4], 16)?;
let alpha = 100;
// Duplicate bytes
let red = red * 17;
let green = green * 17;
let blue = blue * 17;
return Ok(Color::new(red, green, blue, alpha));
},
_ => {
return Err(ParseColorError::InvalidFormatError);
}
}
} else if &s[0..5] == "rgba(" && &s[s.len()-1..s.len()] == ")" {
// rgba(101, 255, 255, 100)
let parts: Vec<&str> = s[4..].trim_matches(|p| p == '(' || p == ')')
.split(',')
.collect();
if parts.len() != 4 {
return Err(ParseColorError::InvalidFormatError);
}
let red = parts[0].parse::<u8>()?;
let green = parts[1].parse::<u8>()?;
let blue = parts[2].parse::<u8>()?;
let alpha = parts[3].parse::<u8>()?;
return Ok(Color::new(red, green, blue, alpha));
} else if &s[0..4] == "rgb(" && &s[s.len()-1..s.len()] == ")" {
// rgb(101, 255, 255)
let parts: Vec<&str> = s[3..].trim_matches(|p| p == '(' || p == ')')
.split(',')
.collect();
if parts.len() != 3 {
return Err(ParseColorError::InvalidFormatError);
}
let red = parts[0].parse::<u8>()?;
let green = parts[1].parse::<u8>()?;
let blue = parts[2].parse::<u8>()?;
let alpha = 100;
return Ok(Color::new(red, green, blue, alpha));
}
return Err(ParseColorError::InvalidFormatError);
}
}
如果是 16 进制的写法, 我们用这样的方式, 把各个通道的颜色值转换为 u8 类型:
let red = u8::from_str_radix(&s[1..3], 16)?;
let green = u8::from_str_radix(&s[3..5], 16)?;
let blue = u8::from_str_radix(&s[5..7], 16)?;
let alpha = u8::from_str_radix(&s[7..9], 16)?;
这里要注意的一点是, u8::from_str_radix()
的返回值类型是 Result<u8, std::num::ParseIntError>
,
我们实现了 ParseIntError
到 ParseColorError
的转换:
impl From<std::num::ParseIntError> for ParseColorError {
fn from(_error: std::num::ParseIntError) -> Self {
Self::InvalidFormatError
}
}
如果要处理 10 进制的颜色值, 我们用这样的方式为读取通道值:
let red = parts[0].parse::<u8>()?;
let green = parts[1].parse::<u8>()?;
let blue = parts[2].parse::<u8>()?;
let alpha = parts[3].parse::<u8>()?;
最后, 测试了几种不同的颜色格式能不能正常被解析:
let colors = [
"#fea",
"#ffeeaa",
"#ffeeaa99",
"rgb ( 255, 255, 200)",
"rgba ( 255, 255, 200, 100)",
];
for color_str in &colors {
let color = color_str.parse::<Color>();
println!("color_str: {}, color: {:?}", color_str, color);
}