rust

Rustでimplの実装を複数ファイルに書く

発端

小ネタ。お題そのままであるが、implの中身が長くなると、分けたくなる。そのためだけに別の空structを作って処理を移譲するのはなんか躊躇う。

implは分けられる

ところが、公式にもある通り、そもそもimplは複数のブロックに分けて書ける。

src/lib.rs
struct hyper_so_cool {
  f1: u8,
  f2: String,
}

impl hyper_so_cool {
  fn f1(self) {
    // do something wonderful...
  }
}

impl hyper_so_cool {
  fn f2(&self) {
    // do something marvelous...
  }
}

impl hyper_so_cool {
  fn f3(&mut self) {
    // do something fantastic...
  }
}

ファイル分割してみる

単一ファイルの中でこの書き方をしてもあんまり意味がないので、この機能は多分ファイル分割を意識したもののはず。ほな、分けましょう。単純にモジュールに分けてみます。

lib.rs
mod struct_definition; // include struct hyper_so_cool
mod module1; // include func f1
mod module2; // include func f2
mod module3; // include func f3
src/struct_definition.rs
struct hyper_so_cool {
  f1: u8,
  f2: String,
}
src/module1.rs
use super::hyper_so_cool;

impl hyper_so_cool {
  fn f1(self) {
    // do something wonderful...
  }
}
src/module2.rs
use super::hyper_so_cool;

impl hyper_so_cool {
  fn f2(&self) {
    // do something marvelous...
  }
}
src/module3.rs
use super::hyper_so_cool;

impl hyper_so_cool {
  fn f3(&mut self) {
    // do something fantastic...
  }
}

hyper_so_coolの定義をuseする必要があります。そりゃそうですね。

pubが必要になる

さて、この状態でcargo runしようとすると、実はエラーが出ます。
なんと、hyper_so_coolprivateなのでアクセスできないと言われます。
仕方がないので、公開します。

src/struct_definition.rs
pub struct hyper_so_cool { // pub!
  f1: u8,
  f2: String,
}

メソッド内ではたいていの場合、それぞれのフィールドにアクセスする必要があるでしょうから(じゃなかったらメソッドにする意味はだいぶ限定されてしまう)、これだけでは不充分で、結局全部pubをつける羽目になる。

src/struct_definition.rs
pub struct hyper_so_cool { // pub!
  pub f1: u8, // pub! pub!
  pub f2: String, // pub! pub! pub!
}

すごく釈然としない。家の鍵を自分で壊した感。Rustの場合、「ファイル分割=モジュールを分ける」ということになってしまう(んですよね?)ので、こういうことになる。

サブモジュールにしたら?

でもまあ、もう一歩進んで考えてみると、後からこのstructを使いたい場合、外側からはmodule1 module2 module3を全てuseする必要があるので、使いにくい。普通はどうするのか、と考えると、サブモジュールである。以下のディレクトリ構成にしよう。

src
  hyper_so_cool
    mod.rs
    module1.rs
    module2.rs
    module3.rs

そしてコードは以下。

src/hyper_so_cool/mod.rs
mod module1;
mod module2;
mod module3;

struct hyper_so_cool { // no more pub!
  f1: u8, // no more pub!
  f2: String, // no more pub!
}

module1.rs module2.rs module3.rsに関しては、src/hyper_so_cool以下へ移動するだけで中身は変わらず。

というわけで、こう書くと各サブモジュールから親のstructへアクセスが可能になるみたいです。
これからはこう書きます。普通は自然とそうなるのかもしれないけれど、サブモジュールからのaccessibilityの話ってあんまり見たことがないので書いてみました。

まとめ

Rustで単一structに対するimplを複数に分けたい場合は、サブモジュールにして、mod.rsにstruct定義を、それぞれのサブモジュールにimplを書くようにすれば、struct定義にやたらめったらpubをつけることを防ぐことができます。

追記

@termoshttさんに、コメントで教えていただきました。ありがとうございます!
pub(crate)を使うと、同一crate内からのアクセスだけを許すようになるみたいです。

   // pub(crate) makes functions visible only within the current crate
    pub(crate) fn public_function_in_crate() {
        println!("called `my_mod::public_function_in_crate()");
    }