分类

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>, 我们实现了 ParseIntErrorParseColorError 的转换:

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