【Rust】コード分割/ファイル分割して簡潔なコードを書く

コード分割はプログラムを簡潔にわかりやすく保つためにもとても重要なものです。

どのプログラミング言語を使っていてもコード分割は常に心がけたいことですが、Rustではどのようにコード分割すれば良いのでしょうか?

今回はRustでコード分割するうえで必要な知識と実際にコード分割する方法を紹介します。

この記事でわかること
  • Rustでコード分割するうえで必要な知識
  • コード分割手法
  • 分割したコードの使用方法
目次

基本構造

まず初めにコード分割するうえで必要となるRustの基本構造について見ていきましょう。

小さなものから順にモジュールクレートパッケージワークスペースというものがあります。

モジュール

モジュールはコードのまとまりです。

言語にもよりますが、例えば名前空間(namespace)が似ています。

実装としては以下のようにmodキーワードを使用します。

mod foo {
  mod bar {
    fn test() {}
  }
}

クレート

クレートについて公式では以下のように記載されています。

クレートはバイナリかライブラリのどちらかです。

パッケージとクレート – The Rust Programming Language

要するに他言語におけるパッケージやライブラリと同じ認識で良いかと思います。(厳密には違う部分もあるかもしれませんが。。)

クレートには2種類ありそれぞれ以下のようなものとなります。

バイナリクレート

単体で実行可能なもの(.exeなど)

ライブラリクレート

単体では実行できないもの(.lib, .dll, .aなど)

構造的にはsrc/main.rs,src/lib.rs,src/bin/*.rsを頂点(クレートルート)としたモジュールツリー(一連のコードのまとまり)となっています。

構造のサンプル

「構造」や「モジュールツリー」と字面だけ見てもわかりにくいのでサンプルを作ってみます。

ディレクトリ/ファイル構造としては以下のようなものとします。

sample/
|-- Cargo.lock
|-- Cargo.toml
`-- src/
    |-- lib.rs
    |-- module1.rs
    |-- module2.rs
    |-- module1/
    |   |-- module11.rs
    |   `-- module12.rs
    `-- module2/
        |-- module21.rs
        `-- module22.rs

コードは以下のようにします。
ここでは実装の詳細は気にせずにモジュールのつながりのみ意識します。

mod module1;
mod module2;
...
pub mod module11;
pub mod module12;
pub mod foo {}
pub mod bar {}
pub mod module21;
pub mod module22;
pub mod hoge {}
pub mod fuga {}

すると今回作成したものは以下のようなクレートとなります。

  • ライブラリクレート
  • lib.rsをクレートルートとする
  • モジュールツリーは以下に示す構造となる
crate root
├── module1
│   ├── module11
│   │   └── foo
│   └── module12
│       └── bar
└── module2
    ├── module21
    │   └── hoge
    └── module22
        └── fuga

パッケージ

パッケージとは以下のような特徴を持つものです。

  • 提供する機能のまとまり
  • 1つ以上のクレートを持つ
  • cargo newを実行してできるもの
  • Cargo.tomlが存在するディレクトリをルートとしたまとまり

試しにcargo newしてパッケージを作ってみましょう。

% cargo new my-package
     Created binary (application) `my-package` package

ディレクトリ構造をみると以下のようになっています。

my-package/
├── Cargo.toml
└── src/
    └── main.rs

これがパッケージとなります。

上記は単純な例ですが、パッケージであるためには以下のルールに従う必要があります。

  • 少なくとも1つ以上のクレート(バイナリクレートもしくはライブラリクレート)を持つ
  • バイナリクレートは複数持つことができる
  • ライブラリクレートは最大で1つまで持つことができる

この説明だけだとパッと理解しにくいかと思います。
そのため実際に作成できるパッケージのパターンを下記の記事にて紹介していますので合わせてご覧ください。

ワークスペース

ワークスペースは複数のパッケージをまとめて管理するためのものです。

Cargo.lockや出力ディレクトリ(target)がひとつになるなどの利点がありますが、詳細は下記をご覧ください。

本記事の内容においてはパッケージを分割するために利用するものという理解でOKです。

実際の使用方法は後述のパッケージ分割にて紹介します。

コード分割の方法

ここまでのことを踏まえて以下の2点をやってみましょう。

  • コード分割/ファイル分割する
  • 分割したコードを別のコードから利用する

モジュール分割

分割

モジュール分割の方法はRust 2018エディションより前か後かで若干変わっています。

本記事ではRust2018以降の方式でやります。

STEP
クレートの作成

下記コマンドでライブラリクレートを含むパッケージを作成します。

cargo new sample --lib
  • 既にlib.rsが存在する場合は新たに作成する必要はありません。
  • バイナリクレート(main.rs)でもOKです。
STEP
モジュールディレクトリの作成

srcディレクトリ配下にmy_moduleディレクトリを作成します。

sample/
|-- Cargo.toml
`-- src/
    |-- lib.rs
    `-- my_module/
STEP
子モジュールの作成

my_moduleディレクトリ配下にfoo.rsbar.rsを追加します。

sample/
|-- Cargo.toml
`-- src/
    |-- lib.rs
    `-- my_module/
        |-- bar.rs
        `-- foo.rs
pub fn show() {
    println!("foo!");
}
pub fn show() {
    println!("bar!");
}
STEP
親モジュールの作成

srcディレクトリ配下にmy_module.rsを作成します。

sample/
|-- Cargo.toml
`-- src/
    |-- lib.rs
    |-- my_module.rs
    `-- my_module/
        |-- bar.rs
        `-- foo.rs
pub mod foo;
pub mod bar;

ファイル名はStep2で作成したディレクトリ名と同じ名前にしましょう。

呼び出し

分割したモジュールを使うには以下のようにmodキーワードでモジュールを参照します。

mod my_module;

pub fn show() {
    my_module::foo::show();
    my_module::bar::show();
}

useを使用することでパスを短縮して使用することもできます。

mod my_module;
use my_module::{foo, bar};

pub fn show() {
    foo::show();
    bar::show();
}

クレート分割

分割

クレートを分割する場合は上記で説明したパッケージの構成ルールに従う必要があります。

ここでは例としてバイナリクレート+ライブラリクレートにコードを分割してみます。

STEP
バイナリクレートの作成(パッケージの作成)

下記コマンドでバイナリクレートを保持するパッケージを作成します。

cargo new sample
STEP
ライブラリクレートの作成

srcディレクトリにlib.rsを追加します。

pub fn foo() {
    println!("foo!");
}
STEP
ファイル構造の確認

以下のファイル構造となっていることを確認します。

sample/
|-- Cargo.toml
`-- src/
    |-- lib.rs
    `-- main.rs

呼び出し

バイナリクレート(main.rs)からライブラリクレート(lib.rs)を使用してみます。

この場合はmain.rsから以下のようにuseを使用するだけでOKです。

use sample::foo;

fn main() {
    foo();
    println!("Hello, world!");
}

上記のuse sample::foosample部分はパッケージ名に依存します。

正確にはCargo.tomlの以下となります。

[package]
name = "sample"
version = "0.1.0"
edition = "2021"

パッケージ分割

分割

パッケージを分割するためにはワークスペースを利用します。

STEP
ワークスペースディレクトリを作成する

空のディレクトリを作成します。

ここではmy-workspaceという名前で作成します。

この段階でのファイル構造は以下のようになります。

my-workspace/
STEP
パッケージの作成

サンプルとして以下のようなパッケージを作成します。

package1

バイナリクレートを持つパッケージ

package2

ライブラリクレートを持つパッケージ

下記コマンドでサクッと作成しましょう。

cargo new package1
cargo new package2 --lib

この段階でのファイル構造は以下のようになります。

my-workspace/
|-- package1/
|   |-- Cargo.toml
|   `-- src/
|       `-- main.rs
`-- package2/
    |-- Cargo.toml
    `-- src/
        `-- lib.rs
STEP
Cargo.tomの作成

my-workspace/Cargo.tomlを以下の内容で新規作成します。

[workspace]
members = [
    "package1",
    "package2",
]

呼び出し

ではpackage1からpackage2を実際に使用してみましょう。

STEP
依存関係の追加

package1からpackage2を使用するためにpackage1/Cargo.tomlに依存関係を追加します。

[package]
name = "package1"
version = "0.1.0"
edition = "2021"

[dependencies]
package2 = { path = "../package2" }
STEP
使用したいパッケージを呼び出す

後はuseを用いて今いるスコープに使用したい関数などを取り込むだけです。

use package2::add;

fn main() {
    let result = add(2, 3);
    println!("result={}", result);
}

別パッケージの呼び出しの際にextern crateという構文を使用しているものが見られますが、Rust2018以降は基本的に使用しなくてOKなものです。

詳細は下記をご参照ください。

最後に

Rust特有の言葉に戸惑うこともありますが基本的な考えは他言語と一緒です。

構造も少し複雑で手数が多いなという印象ですが、堅牢なコードを書くためにはこれくらいしっかいしているほうが安心感があります。

よかったらシェアしてね!
  • URLをコピーしました!
目次