Rustのモジュールシステムが厳格
goは1年半ぐらい結構書いてきて、シンタックスもそこまで複雑ではないので大分慣れてきた気がした。 なので最近はrustをちょくちょく書いてる。
今はgoで書いた8086のディスアセンブラをrustで少しずつ書き直そうと思ってる
クローラとかは若干飽きたため。monkeyインタプリタでもよかったけど、これは最近書いたばっかでしっかり記憶に残っちゃっているのでほぼ忘れたディスアセンブラにした。
で、rustはgoのパッケージシステムと似たようなモジュールシステムがある。というか書き方を見た感じC++とほぼ一緒なのだろうか。
rustのモジュールの切り方はgoのパッケージのように気楽に切ることはできない。なぜならimportに明確に親子関係が定められているから。
goの場合例えば下記のようなパッケージ構成のとき、
. ├── ast │ ├── ast.go │ └── ast_test.go ├── evaluator │ ├── builtins.go │ ├── evaluator.go │ └── evaluator_test.go ├── lexer │ ├── lexer.go │ └── lexer_test.go ├── main.go ├── object │ ├── environment.go │ └── object.go ├── parser │ ├── parser.go │ ├── parser_test.go │ └── parser_tracing.go ├── repl │ └── repl.go └── token └── token.go
当然だが、replパッケージからparser, astパッケージをimportできる。
しかし、rustのモジュールシステムの場合はそれができない。
例えばrustで以下のようにモジュールを切ってみる。
. ├── disassembler │ ├── mod.rs │ └── reader.rs ├── hoge │ └── mod.rs └── main.rs
disassemblerのreaderからhogeを参照しようとしてみる
コードは次のようになる
hoge/mod.rs
pub fn fuga() { println!("fuga"); }
disassembler/reader.rs
use std::vec::Vec; mod hoge; pub fn start(input: Vec<u8>) { hoge::fuga(); let mut da: DisAssembler8086 = DisAssembler::new(input); println!("{:?}", da.body); da.dump_cur_token(); da.next(); da.dump_cur_token(); while !da.is_end() { da.next(); } da.dump_cur_token(); } ・・・ 以下略
これをビルドしようとすると次のエラーが出る
error[E0583]: file not found for module `hoge` --> src/disassembler/reader.rs:2:5 | 2 | mod hoge; | ^^^^ | = help: name the file either reader/hoge.rs or reader/hoge/mod.rs inside the directory "src/disassembler" error: aborting due to previous error For more information about this error, try `rustc --explain E0583`.
エラーの通り mod hoge
とreader内で書くと src/disassembler/reader/hoge/mod.rs
か src/disassembler/reader/hoge.rs
を探そうとする。
つまり親ディレクトリを経由したmodの参照はできないということ(もし参照できる方法あったらすいません)。
これは同一階層でのディレクトリ同士では完全に実装が依存しないようになるので結構良い気がする。
まとめ
- rustでモジュールを切るときは依存関係をちゃんと考えて切るようにしよう
- 同一階層内で使いたい場合は同じディレクトリにファイルを分けるようにする
まだ全然知見がないけどgoとは全然違う厳しさがあるので面白い