Rustでimplの実装を複数ファイルに書く
発端
小ネタ。お題そのままであるが、implの中身が長くなると、分けたくなる。そのためだけに別の空structを作って処理を移譲するのはなんか躊躇う。
implは分けられる
ところが、公式にもある通り、そもそもimplは複数のブロックに分けて書ける。
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... } }
ファイル分割してみる
単一ファイルの中でこの書き方をしてもあんまり意味がないので、この機能は多分ファイル分割を意識したもののはず。ほな、分けましょう。単純にモジュールに分けてみます。
mod struct_definition; // include struct hyper_so_cool mod module1; // include func f1 mod module2; // include func f2 mod module3; // include func f3
struct hyper_so_cool { f1: u8, f2: String, }
use super::hyper_so_cool; impl hyper_so_cool { fn f1(self) { // do something wonderful... } }
use super::hyper_so_cool; impl hyper_so_cool { fn f2(&self) { // do something marvelous... } }
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_cool
がprivate
なのでアクセスできないと言われます。
仕方がないので、公開します。
pub struct hyper_so_cool { // pub! f1: u8, f2: String, }
メソッド内ではたいていの場合、それぞれのフィールドにアクセスする必要があるでしょうから(じゃなかったらメソッドにする意味はだいぶ限定されてしまう)、これだけでは不充分で、結局全部pub
をつける羽目になる。
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
そしてコードは以下。
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
をつけることを防ぐことができます。
追記
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()"); }