Rustのモジュール分割事例(コード分割、テスト分割)

## 概要
Rustのコードやテストのモジュール分割に関するノウハウを記載します。

これまで載せてきたサンプルなど規模が大きくなっており、いくつかモジュール分割する必要に迫られて調べた内容です。ここでは、モジュールの可視性にはさほど重点を置いていません。

リリース用途というより、学習用途ですが試していくうちにモジュールの知識が深まっていくと思います。

### 環境

* rustc 1.57.0 (Rust 2021)
* WSL1 (Ubuntu 18.04 LTS)

## 実装のプロトタイプごとにモジュールを分ける
通常はバージョン管理はGitまかせなので必要ないのですが Rust の学習においては改善の過程を記録しておくことが役立つこともあります。

## 事例) モジュール分割をする

### 0. 分割前のコード

```
+ src/
  + lib.rs
  + main.rs  
```

#### Cargo.toml

```toml
[package]
name = "playground"
version = "0.1.0"
edition = "2021"

[dependencies]
```

#### src/lib.rs

```rust
/// # Examples
///
/// ```
/// use playground::version;
/// assert_eq!(version(), 1);
/// ```
pub fn version() -> u8 { 0 }

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(version(), 0);
    }
}
```

#### src/main.rs

```rust
use playground::version;

fn main() {
    assert_eq!(version(), 0);
}
```

### 1. バージョン分割後(lib.rsはまだひと塊の状態)

```
+ src/
  + lib.rs
  + bin/
    + playground-v1.rs
    + playground-v2.rs
```

#### src/lib.rs

テストコードは同じように `tests` に置き続けることもできますが、今回は各 v1, v2 ごとに別々にしています。バージョンごとに実装の進み具合が異なり、テスト内容が異なることは良くあるからです。

Docテストの方は、use でモジュールが v1 か v2 かを意識する必要がありますが、 v1::tests と v2::tests はそれぞれ v1, v2 を埋め込まずに書けることに注目してください。これは、テストコードの流用がしやすくなることを意味します。

```rust
pub mod v1 {
    /// # Examples
    ///
    /// ```
    /// use playground::v1::version;
    /// assert_eq!(version(), 1);
    /// ```
    pub fn version() -> u8 { 1 }

    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn it_works() {
            assert_eq!(version(), 1);
        }
    }
}

pub mod v2 {
    /// # Examples
    ///
    /// ```
    /// use playground::v2::version;
    /// assert_eq!(version(), 2);
    /// ```
    pub fn version() -> u8 { 2 }

    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn it_works() {
            assert_eq!(version(), 2);
        }
    }
}
```

* 注意
  * `#[cfg(test)]` を忘れないようにしてください  
　　関数を含めて多くは名前の重複は影響はありませんが、`#[cfg(test)]` がない場合には、テスト時の use で必要なのにインポート不要と言われたことがありました。コンパイルの一部として解釈されることが影響しているのかもしれません。

#### src/bin

main.rs も分割できます。配布物に含める場合は Cargo.toml の編集が必要ですが、ここでは実験用途ぐらいなので、 src/bin にファイルを置くことにします。

コマンドが複数となる場合は、 `--bin` オプションでコマンド名を指定せよ…と具体的なコマンドの候補も合わせて教えてくれます。親切ですね。

```console
$ cargo run
error: `cargo run` could not determine which binary to run. Use the `--bin` option to specify a binary, or the `default-run` manifest key.
available binaries: playground-v1, playground-v2
```

##### src/bin/playground-v1.rs

`cargo run --bin playground-v1` といった風に実行できます。

```rust
use playground::v1::version;

fn main() {
    assert_eq!(version(), 1);
}
```

##### src/bin/playground-v2.rs

`cargo run --bin playground-v2` といった風に実行できます。

```rust
use playground::v2::version;

fn main() {
    assert_eq!(version(), 2);
}
```

### 2. バージョン分割後(lib.rsのコードを分割する)

各バージョンのコードが増えていくとファイル分割したくなりますよね。
こんな風に分けます。

```
+ src/
  + lib.rs
  + v1.rs
  + v2.rs
  + bin/
    + playground-v1.rs
    + playground-v2.rs
```

#### src/lib.rs

```rust
pub mod v1;
pub mod v2;
```

#### src/v1.rs

```rust
pub fn version() -> u8 { 1 }

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(version(), 1);
    }
}
```

#### srv/v2.rs

```rust
pub fn version() -> u8 { 2 }

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(version(), 2);
    }
}
```

### 3. バージョン分割後(テストコードを分割する)

`v1` のモジュール配下なので `v1` のディレクトリを作成し、 `tests.rs` (モジュール名.rs) の名前でテストの中身を移します。

テストコードの量が多くなってくると可読性が高まりますね。

```
+ src/
  + lib.rs
  + v1.rs
  + v1/
    + test.rs
  + v2.rs
  + v2/
    + test.rs
  + bin/
    + playground-v1.rs
    + playground-v2.rs
```

#### src/v1.rs

```rust
/// # Examples
///
/// ```
/// use playground::v1::version;
/// assert_eq!(version(), 1);
/// ```
pub fn version() -> u8 { 1 }

#[cfg(test)]
mod tests;
```

#### src/v1/test.rs

```rust
use super::*;

#[test]
fn it_works() {
    assert_eq!(version(), 1);
}
```

## おまけ) モジュールのpath指定のサンプル

分割の仕方は説明し終えたので、path指定のサンプルをいくつか出しておきます。上の `super` なりでやっていることの理解が深まると思います。

### self指定の例

self は子モジュールを参照する場合に使います。

**src/lib.rs**

```rust
pub mod foo {
    pub fn sum() -> u8 { self::a::index() + self::b::index() }
    pub mod a {
        pub fn index() -> u8 { 1 }
    }
    pub mod b {
        pub fn index() -> u8 { 2 }
    }
}

#[cfg(test)]
mod tests {
    use super::foo;

    #[test]
    fn it_works() {
        assert_eq!(foo::sum(), 3);
        assert_eq!(foo::a::index(), 1);
        assert_eq!(foo::b::index(), 2);
    }
}
```

### super指定の例

上で何度か出てきていますが、親や兄弟を参照するときに使います。
一般的にコマンドラインで `cd ..` などと親ディレクトリのパスを指定したりしますが、 `super` は `..` の代わりのものと理解すると簡単ですね。

**src/lib.rs**

```rust
pub mod foo {
    pub mod v1 {
        pub mod bar {
            use std::ops::Add;

            pub fn f1() {}
            pub fn f2() {}
            pub fn add<T: Add<Output=T>>(a: T, b: T) -> T {
                a.add(b)
            }
        }
        mod tests {
            #[test]
            fn test_v1() {
                super::bar::f1();
                super::bar::f2();
                assert_eq!(
                    super::bar::add(1, 2), 3
                );
            }
        }
    }
    pub mod v2 {
        pub mod bar {
            use std::ops::Add;

            pub fn f1() {}
            pub fn f2() {}
            pub fn add<T: Add<Output=T>>(a: T, b: T) -> T {
                a.add(b)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_v2() {
        use super::foo::v2::bar;
        use super::foo::v2::bar::{f1, f2};
        f1();
        f2();
        assert_eq!(bar::add(1, 2), 3);
    }
}
```

### 可視性

#### 親モジュールを非公開にして子モジュールを公開する

**Cargo.toml**

```toml
[package]
name = "vsby-hide-parent-m"
version = "0.1.0"
edition = "2021"

[dependencies]
```

**src/lib.rs**

`pub use` の行がポイントですね。 `inner_mod` は隠されて one だけが見えます。あと、`pub` を外すと外部モジュールからは見えなくなるので、必然的にテストコードは子モジュールに配置することになります。

```rust
pub use inner_mod::one;

mod inner_mod { // private
    fn inner_one() -> u8 { 1 }
    pub fn one() -> u8 { inner_one() }

    #[cfg(test)]
    mod tests {
        #[test]
        fn it_works() {
            assert_eq!(super::inner_one(), 1);
            assert_eq!(super::one(), 1);
        }
    }
}
```

**src/main.rs**

```rust
use vsby_hide_parent_m::one;

fn main() {
    assert_eq!(one(), 1);
}
```

## まとめ

これまで様々な言語(Java, Python, etc)のモジュール構成を見てきましたが Rust はいいですね。
use で内部的な階層を隠ぺいでき、その結果としてJavaにあるような名前空間の長々しいモジュール構成などから解放されます。
これがモジュール階層における仕様の一つの到達点に思えます。

なお別の見方をすると Cライブラリの関数の公開の仕方と似ているとも言える。Cライブラリは内部の構成は(見えなくもないですが建前上)隠ぺいされていて、公開用の関数以外は内部仕様であり非互換を気にせず置き換えられます。この柔軟性と特徴をうまくルール化したというのがRustのモジュール仕様で公開する方にも使う方にも程よい制約を与えてくれますね。

## 参考
* [Test Organization - The Rust Programming Language](https://doc.rust-lang.org/book/ch11-03-test-organization.html)