Rust: FromStr trait

Chuỗi bài viết Rust Tiếng Việt là một trong những nội dung nằm trong sách Rust Tiếng Việt

FromStr là một trait để khởi tạo instance từ string trong Rust, nó tương đương abstract class nếu bạn có background OOP.

pub trait FromStr {
  type Err;
  fn from_str(s: &str) -> Result<Self, Self::Err>;
}

Thường phương thức from_str của FromStr thường được ngầm định sử dụng thông qua phương thức parse của str. Ví dụ:

// Thay vì
let one = u32::from_str("1");

// thì sử dụng phương thức parse
let one: u32 = "1".parse().unwrap();
assert_eq!(1, one);

// parse() sử dụng turbofish ::<>
let two = "2".parse::<u32>();
assert_eq!(Ok(2), two);

let nope = "j".parse::<u32>();
assert!(nope.is_err());

parse là một phương thức general nên thường được sử dụng với kiểu dữ liệu như trên hoặc sử dụng turbofish ::<> để thuật toán inference có thể hiểu để parse thành đúng kiểu bạn cần.

Parse str to Struct

Bạn có 1 struct và muốn parse 1 str thành struct đó, bạn sẽ cần impl trait FromStr

use std::str::FromStr;
use std::num::ParseIntError;

#[derive(Debug, PartialEq)]
struct Point {
  x: i32,
  y: i32
}

impl FromStr for Point {
  type Err = ParseIntError;

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    let coords: Vec<&str> = s.trim_matches(|p| p == '(' || p == ')' )
                               .split(',')
                               .collect();

    let x_fromstr = coords[0].parse::<i32>()?;
    let y_fromstr = coords[1].parse::<i32>()?;

    Ok(Point { x: x_fromstr, y: y_fromstr })
  }
}

// Có nhiều cách
let p: Point = "(1,2)".parse();
let p = "(1,2)".parse::<Point>();
let p = Point::from_str("(1,2)");

assert_eq!(p.unwrap(), Point{ x: 1, y: 2} )

Parse str to Enum

Một điều mình thấy để code dễ đọc, dễ maintain hơn là chúng ta nên tránh sử dụng stringly-typed apis. Ví dụ như:

fn print(color: &str, text: &str) { ... }
print("Foobar", "blue");

Thay vì đó mà hãy sử dụng enum:

enum Color { Red, Green, CornflowerBlue }

fn print(color: Color, text: &str) { ... }
print(Green, "duyet");

Cũng nên hạn chế sử dụng quá nhiều Boolean, thực tế Boolean cũng chỉ là

enum bool { true, false }

Thay vào đó hãy tự định nghĩa enum cho các ngữ cảnh khác nhau để code dễ đọc hơn:

enum EnvVars { Clear, Inherit }
enum DisplayStyle { Color, Monochrome }

Chúng ta implement std::str::FromStr trait như sau:

use std::str::FromStr;

#[derive(Debug, PartialEq)]
enum Color {
  Red,
  Green,
  Blue
}

impl FromStr for Color {
  type Err = ();

  fn from_str(input: &str) -> Result<Color, Self::Err> {
    match input {
      "red"   => Ok(Color::Red),
      "green" => Ok(Color::Green),
      "blue"  => Ok(Color::Blue),
      _       => Err(()),
    }
  }
}

let c: Color = "red".parse().unwrap();
assert_eq!(c, Color::Red);

References