Rust で TDD Boot Camp （課題4:「閉区間／開区間への機能追加」）Traitでのメソッド記述, 正規表現


以下ページの転載になります。ご了承ください。

[Rustを0から学んでみた 〜Part.4〜 Traitでのメソッド記述、正規表現 編（TDD Boot Camp 課題4「閉区間／開区間への機能追加」） - Yuto Hongo Portfolio](https://hongo.dev/blog/learn-rust-part4-trait-regex)

---

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


- [TDD Boot Camp(TDDBC) - TDDBC仙台03/課題](http://devtesting.jp/tddbc/?TDDBC%E4%BB%99%E5%8F%B003%2F%E8%AA%B2%E9%A1%8C)
- [TDD Boot Camp(TDDBC) - TDDBC仙台03/課題用語集](http://devtesting.jp/tddbc/?TDDBC%E4%BB%99%E5%8F%B003%2F%E8%AA%B2%E9%A1%8C%E7%94%A8%E8%AA%9E%E9%9B%86)



今までの記事は以下となっています。


- [Rustを0から学んでみた 〜Part.0〜 準備 / 概要 編](https://hongo.dev/blog/learn-rust-part0-introduction-and-setup)
- [Rustを0から学んでみた 〜Part.1〜 struct, impl / テストコードの基礎 編（TDD Boot Camp 課題1「閉区間」）](https://hongo.dev/blog/learn-rust-part1-struct-impl-test)
- [Rustを0から学んでみた 〜Part.2〜 trait / modの管理方法 編（TDD Boot Camp 課題2「開区間」）](https://hongo.dev/blog/learn-rust-part2-trait-module)
- [Rustを0から学んでみた 〜Part.3〜 generics（で、オーバーロード） 編（TDD Boot Camp 課題3「閉区間と開区間」）](https://hongo.dev/blog/learn-rust-part3-generics-trait-overload)


---


[<u>ひとことで言うとこんな記事</u>]


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


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

---


[<u>目次</u>]

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


---



## 課題4： 閉区間／開区間への機能追加

### 課題4-1

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

### 課題4-2

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

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

### containsAll の例

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

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

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

<u>[range.rs](https://github.com/h0ng0yut0/tddbc-rust-practice/blob/f3765b6e7dac9b7aa095b159928785d65a612318/src/range.rs)</u>

```rust
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を実装してみました。なんだか、is_connected_toとget_intersectionが似たような感じに思えたので、共通化してみようと思いました。


```rust
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;`を以下のように簡単にしたいと思いました。

```rust
  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自身にデフォルトで記述してしまうことにしました__ 。

<u>[range.rs](https://github.com/h0ng0yut0/tddbc-rust-practice/blob/f3765b6e7dac9b7aa095b159928785d65a612318/src/range.rs)</u>

```rust
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を追加

<u>Cargo.toml</u>

```toml
[dependencies]
regex = "0.1" 
```

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

<u>OpenRange.rs</u>等

```rust
extern crate regex;
use regex::Regex;
``` 

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

```rust
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`したものの中身は以下のようになっています。

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

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

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

```rust
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](https://rustexp.lpil.uk/)

正規表現テスターサイト



## 今回の実装の Pull Request

[課題4： 閉区間／開区間への機能追加 by h0ng0yut0 · Pull Request #5 · h0ng0yut0/tddbc-rust-practice](https://github.com/h0ng0yut0/tddbc-rust-practice/pull/5)

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

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

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

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

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