Rust で TDD Boot Camp （課題2:「開区間」）trait, modの管理方法


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

[Rustを0から学んでみた〜Part.2〜 trait / modの管理方法 編（TDD Boot Camp 課題2「開区間」）](https://hongo.dev/blog/learn-rust-part2-trait-module)

---

「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)

こちらの記事読了が前提の内容となっております。 

[![learn-rust-part1-struct-impl-test-thumb](//images.ctfassets.net/yyxwl1h0rr37/6HR5sLyOO3I7HBrM69YFw5/c2cf8f0349eee0ecae89c1c679ef21f1/Rust.002.png?w=720&q=20)Rustを0から学んでみた〜Part.1〜 struct, impl, テストコードの基礎編（TDD Boot Camp 課題1「閉区間」）](https://hongo.dev/blog/learn-rust-part1-struct-impl-test "Rustを0から学んでみた〜Part.1〜 struct, impl, テストコードの基礎編（TDD Boot Camp 課題1「閉区間」）")

---

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

以下2点を学ぶ事ができます

- trait（トレイト）を利用した、共通の振る舞いの定義
- 基本的なmod（モジュール）の構成とファイル配置方法

---


[<u>こんなひとにおすすめ</u>]

- 「これからRustを勉強していきます！」という方
- 「Rustってclassないの？！」と驚いている方
- Rustの初歩的なモジュール構成と配置方法を知りたい方


---

[<u>目次</u>]

- 課題2： 整数の開区間（Open Range）
- 学んだこと １：trait（トレイト）を利用した、共通の振る舞いの定義
- 学んだこと ２：モジュール(mod)の構成とファイル配置方法
- 学んだこと その他：cargoがテストコードを認識するのは、tests配下のみ
- 今回の実装の Pull Request
- 次回：TDDBC「課題３：閉区間と開区間」 実施予定


---


## 課題2： 整数の開区間（Open Range）

### 課題2-1

- 下端点と上端点を与えて開区間(new)生成
- 開区間から下端、上端の取得
- 開区間から文字列(to_string)取得

### 課題2-2

- 開区間が指定した整数を含むか(contains)判定

### 課題2-3

- 開区間が別の開区間と等しいか(equals)判定
- 開区間が別の開区間と接続しているか(isConnectedTo)判定



## 学んだこと１：trait（トレイト）を利用した、共通の振る舞いの定義

今回の課題2を見てすぐに気がつくことは、 __課題1と問題文が全部一緒__ ってことですかね。違うのは「閉区間か開区間か」ってところです。



![learn-rust-part2-trait-module-01](//images.ctfassets.net/yyxwl1h0rr37/uGA2yKRRecdtrSkgDekST/3a2569391f490dcf14ad851e67a26984/Rust.003.png?w=720&q=20)


見ての通り、メンバ変数と振る舞いがまったく同じ種類用意することとなるので、共通定義しておいたほうが楽だろうと思った次第です。

こういうときに利用できるものとして、RustではTraitがございます。


> 参考：[トレイト - Rust By Example 日本語版](https://doc.rust-jp.rs/rust-by-example-ja/trait.html)


### Trait（トレイト）の利用方法

#### トレイトの準備：Rangeとして、閉区間と開区間での共通の振る舞いを定義

```rust
pub trait Range {
  fn new(lower: i8, upper: i8) -> Self;
  fn to_string(&self) -> String;
  fn contains(&self, number: i8) -> bool;
  fn equals(&self, range: Self) -> bool;
  fn is_connected_to(&self, range: Self) -> bool;
}
```

共通の振る舞いを上記のように定義しました。


`Self`と記述している部分は、`new()`や`equals()`等の関数は閉区間は閉区間、開区間は開区間を返却するのでそれを示したものとなります。


#### トレイトの利用：閉区間、開区間の実装


閉区間

```rust
// メンバ変数をtraitに定義することはできないそうです
#[derive(Clone)]
pub struct ClosedRange {
  pub lower: i8,
  pub upper: i8,
}

impl Range for ClosedRange {
  fn new(lower: i8, upper: i8) -> ClosedRange {  // Selfを返却
    /**
     * 〜実際の実装〜
     */
  }
}
```

開区間


```rust
// メンバ変数をtraitに定義することはできないそうです
#[derive(Clone)]
pub struct OpenRange {
  pub lower: i8,
  pub upper: i8,
}

impl Range for OpenRange {
  fn new(lower: i8, upper: i8) -> OpenRange {  // Selfを返却
    /**
     * 〜実際の実装〜
     */
  }
}
```


上記のように閉区間、開区間共に実装（impl）を準備してあげればOKです。


もちろん __未実装の振る舞いがある場合にはコンパイルで怒られますので、安心__ ですね。


### 実行：実際にmain.rsで閉区間、開区間を利用する

```rust
use tddbc_rust_practice::range::closed_range::ClosedRange;
use tddbc_rust_practice::range::open_range::OpenRange;
use tddbc_rust_practice::range::Range; // Trait自体もuseしてあげないとfnが見つからない

fn main (){
  let sample_closed_range = ClosedRange::new(1, 5);
  let sample_open_range = OpenRange::new(1, 5);
}
```

__Traitとして振る舞いを定義したもの自体もuseしてあげないと、コンパイル時に振る舞いが見つからずにエラー__ となります。


## 学んだこと２：モジュール(mod)の構成とファイル配置方法

### 膨れ上がっていくlib.rs

OpenRange,ClosedRangeを実装した初期のlib.rsが以下のようなものになっています。

GitHub - h0ng0yut0 [tddbc-rust-practice/lib.rs](https://github.com/h0ng0yut0/tddbc-rust-practice/blob/69e89de52bc123c5b4d41f3b8e27450f5249c691/src/lib.rs)

93行。ちょっと煩雑な気もしてきました。

### RangeとOpenRange,ClosedRangeのモデル通りにモジュール分割


![learn-rust-part2-trait-module-01](//images.ctfassets.net/yyxwl1h0rr37/uGA2yKRRecdtrSkgDekST/3a2569391f490dcf14ad851e67a26984/Rust.003.png?w=720&q=20)


上記モデルを踏まえて、親 `Range`、子 `ClosedRange` `OpenRange`の形でモジュール管理できればいいかなと考えました。


`src`配下のディレクトリ構成です。

```shell-session
.
├── lib.rs
├── main.rs
├── range
│   ├── closed_range.rs
│   └── open_range.rs
└── range.rs
```


`lib.rs` （module準備の大本となるファイル）では、`range`を呼び出すだけ


```rust
pub mod range;
```


`range.rs` には、共通の振る舞いを定義したtraitと、ClosedRangeとOpenRange の実装ファイルを呼び出しています。


```rust
pub mod closed_range;
pub mod open_range;

pub trait Range {
  fn new(lower: i8, upper: i8) -> Self;
  fn to_string(&self) -> String;
  fn contains(&self, number: i8) -> bool;
  fn equals(&self, range: Self) -> bool;
  fn is_connected_to(&self, range: Self) -> bool;
}
```

`closed_range.rs` と `opened_range.rs` には実際の実装がなされています。


closed_rangeの例

```rust
// Range trait を利用することを明示
use crate::range::Range;

#[derive(Clone)]
pub struct ClosedRange {
  pub lower: i8,
  pub upper: i8,
}

impl Range for ClosedRange {
  /*
   * 実際の実装
   */
}
```


関連するモジュールのRustファイルの準備とファイル構成を、階層的に管理していくことが求められるそうです。


こちらのサイトに詳しいまとめ等が載っています。

>参考：[Rustのモジュールの使い方 | κeenのHappy Hacκing Blog](https://keens.github.io/blog/2017/01/15/rustnomoju_runokirikata/)


## 学んだこと その他：cargoがテストコードを認識するのは、tests配下のみ

`tests`ディレクトリ配下も、モジュールと同じ構成にしてみたのですが、全くテストが走らず。。。

結果、落ち着いた構成は以下のような感じです。


```shell-session
.
├── Cargo.lock
├── Cargo.toml
├── README.md
├── src
│   ├── lib.rs
│   ├── main.rs
│   ├── range
│   │   ├── closed_range.rs
│   │   └── open_range.rs
│   └── range.rs
└── tests
    ├── closed_range.rs ← ディレクトリを掘れず tests 配下
    └── open_range.rs ← ディレクトリを掘れず tests 配下
```


## 今回の実装の Pull Request

[課題2:開区間 by h0ng0yut0 · Pull Request #2 · h0ng0yut0/tddbc-rust-practice](https://github.com/h0ng0yut0/tddbc-rust-practice/pull/2)


## 次回：TDDBC「課題３：閉区間と開区間」 実施予定

今回は、trait, modとその管理方法等を学べました。次回の課題は何が学べるのか楽しみです。

- [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)

---



---

最後までお読みいただき、誠にありがとうございます。様々なことを学んでいきたいと思っていますので、内容やソースコード等のアドバイスいただけると幸いです。