Rustを0から学んでみた 〜Part.4〜 Traitでのメソッド記述、正規表現 編(TDD Boot Camp 課題4「閉区間/開区間への機能追加」)

「TDDBCの課題をRustで実施し、言語仕様を学んでいこう!」というシリーズの記事です。

Rustの記事シリーズは以下となっています。


[ひとことで言うとこんな記事]

以下を学ぶ事ができます。

  • Trait自体に関数を実装することもできます
  • Rustに文字列の正規表現のリテラルはない?利用方法がわかります

[目次]

  • 課題4: 閉区間/開区間への機能追加
  • 学んだこと:trait自体に振る舞いを記述できます
  • 学んだこと:Rustに文字列の正規表現のリテラルはない?利用方法はこちら
  • 今回の実装の Pull Request
  • 今回の投稿でシリーズを終了、あきらめた部分

課題4: 閉区間/開区間への機能追加

課題4-1

  • 指定した全ての整数を含むか(containsAll)判定
  • 閉区間と別の閉区間との共通集合(intersection)を取得

課題4-2

  • 文字列表記を解析して(parse)区間を生成

学んだこと:trait自体に振る舞いを記述できます

containsAll の例

事前にcontainsという実装をしているので、すべての値に対してcontainsすればよいとの考えました。

  fn contains_all(&self, numbers: Vec<i8>) -> bool {
    for number in numbers {
      if !self.contains(number) {
        return false;
      }
    }
    return true;
  }

結局これが、 どのStructであっても同じ実装 となることがわかったので、 trait自身にデフォルトで記述してしまうことにしました

range.rs

pub trait SelfRange {
  fn new(lower: i8, upper: i8) -> Self;
  fn to_string(&self) -> String;
  fn contains(&self, number: i8) -> bool;
  fn contains_all(&self, numbers: Vec<i8>) -> bool {
    for number in numbers {
      if !self.contains(number) {
        return false;
      }
    }
    return true;
  }
  fn parse(string: String) -> Self;
}

intersection の例

普通に課題4-1を実装してみました。なんだか、isconnectedtoとget_intersectionが似たような感じに思えたので、共通化してみようと思いました。

pub trait MultiRange<T> {
  fn equals(&self, range: &T) -> bool;
  fn is_connected_to(&self, range: &T) -> bool;
  fn get_intersection(&self, range: &T) -> String;
  fn is_connected_to(&self, range: &T) -> bool;
  fn get_intersection(&self, range: &T) -> String;
  
  // New 共通集合があるかどうかをResult型で確認
  fn intersection(&self, range: &T) -> Result<String, String>;
}

上記の新しいfnである fn intersection(&self, range: &T) -> Result<String, String>;を定義することで、fn is_connected_to(&self, range: &T) -> bool;``fn get_intersection(&self, range: &T) -> String;を以下のように簡単にしたいと思いました。

  fn is_connected_to(&self, range: &T) -> bool {
    match self.intersection(range) {
      Ok(_) => true,
      Err(_) => false,
    }
  }
  
  fn get_intersection(&self, range: &T) -> String {
    self.intersection(range).unwrap()
  }

これが、結局 どのStructであっても、どのジェネリクスの指定であっても同じ実装 となることがわかったので、 trait自身にデフォルトで記述してしまうことにしました

range.rs

pub trait MultiRange<T> {
  fn equals(&self, range: &T) -> bool;
  fn is_connected_to(&self, range: &T) -> bool;
  fn is_connected_to(&self, range: &T) -> bool {
    match self.intersection(range) {
      Ok(_) => true,
      Err(_) => false,
    }
  }
  fn get_intersection(&self, range: &T) -> String {
    self.intersection(range).unwrap()
  }
  fn intersection(&self, range: &T) -> Result<String, String>;
}

ジェネリクストレイトでもうまく表現できると、traitに処理を実装することができますね。

学んだこと:Rustに文字列の正規表現のリテラルはない?利用方法はこちら

利用方法

1.Cargo.tomlの[dependencies]にregexを追加

Cargo.toml

[dependencies]
regex = "0.1" 

2.利用する箇所に以下の記述を追加

OpenRange.rs

extern crate regex;
use regex::Regex;

3.Regexを以下のように利用する

fn parse(string: String) -> OpenRange {
  let regex = Regex::new(r"\((\d+),(\d+)\)").unwrap();
  let caps = regex.captures(&string).unwrap();
  Self::new(
    caps.at(1).unwrap().parse().unwrap(),
    caps.at(2).unwrap().parse().unwrap(),
  )
}

単純にcaptureしたものの中身は以下のようになっています。

// 正規表現にマッチしなかった場合
None

// 正規表現にマッチした場合
Some(Captures({
    0: Some("(1,5)"),
    1: Some("1"), // ← 1つ目の (\d+) の値
    2: Some("5"), // ← 2つ目の (\d+) の値
})),

Capturesにはatというメソッドが準備されており、at(1)などと記述することで1つ目の値が取得できたりします。

const string = "(1,5)"
let regex = Regex::new(r"\((\d+),(\d+)\)").unwrap();

let caps = regex.captures(&string).unwrap(); // 上記 Captures の取得

caps.at(1); // 1つ目の Some("1")が取得できる

parse系だったりregex系はResultだったりOptionだったりで返却されることが他の言語でも多いので、想像しやすいとは思います。

便利サイト

Rustexp

正規表現テスターサイト

今回の実装の Pull Request

課題4: 閉区間/開区間への機能追加 by h0ng0yut0 · Pull Request #5 · h0ng0yut0/tddbc-rust-practice

今回の投稿でシリーズを終了、あきらめた部分

一旦このへんでTDD BCでRustをまなぶシリーズを終了したいとおもいます。

基本的な記述方法等がわかったのですが、そこまで頑張らなくてもいいかと思ってあきらめた点が何点か。

  • 第4回で実施した共通化リファクタリングで新しく定義したメソッドの隠蔽(一応問題文にない振る舞いなので、そとから見えなくしてもいいかもと思った次第)
  • TestCodeを適当にバーっとUnitっぽく記述したが、ホントはSpec的な管理の元テストコードを書きたかった

なにか参考になるもの等がありましたら、ご連絡いただけると幸いです。


[本屋で見たRust本(買おうか悩み中)]


一緒に様々なことを学んでいく仲間を募集しています。

このサイトのお問い合わせなどからご連絡いただけると幸いです。