miseのコードリーディングをしながらRustの勉強するメモ
miseへの移行ついでにコードを読みつつRustの勉強をする試み。
RustはThe Rust Programming LanguageとTake your first steps with Rustをかなり前に少しだけ嗜んだくらい。ソースコードは https://github.com/jdx/mise を読んでいく。
まずはエントリポイントを探す。
The Rust Programming Languageによると、 main
が最初に呼び出される関数になるらしい。こんな基本的なことから忘却している。
These lines define a function named main. The main function is special: it is always the first code that runs in every executable Rust program.
ビルドはCargoと呼ばれるツールで行う。設定ファイルの Cargo.toml
を見ると path = "src/main.rs"
という記述があるので、おそらくここがエントリポイントだろう。
[[bin]]
name = "mise"
path = "src/main.rs"
それっぽい main
関数があるので、ここから読み解いていく。
main
を読み解く前に関数についての基本。
fn sum(a: i32, b: i32) -> i32 {
a + b
}
- 関数は
fn
ではじまる - 関数や変数の命名規則は慣例的にスネークケース
- 関数名の後の () 内に引数を定義する
- 引数は
変数名: 型
で記述する
- 引数は
- 戻り値は
->
の後に型を記述する - 戻り値は暗黙的に最後の式が返される
- セミコロンをつけないと式として評価される
- 明示的に
return
で返すことも可能
- 関数本体は
{}
で囲まれる
慣れ親しんだ他言語に近い文法なので、特に迷わず書けそうなのは有り難い。
これを踏まえて main
関数を見てみると、引数はなく戻り値として eyre::Result<()>
が宣言されている。
fn main() -> eyre::Result<()> {
....
}
Rust By Exampleによると main
の戻り値に Result
を返した場合はエラー発生時にエラーコードなどを出力してくれるようである。
If an error occurs within the main function it will return an error code and print a debug representation of the error (using the Debug trait).
ここでの Result
は core::result::Result だけれど、 eyre::Result
は core::result::Result
の型エイリアスなので同じ文脈で解釈してよさそうか。型エイリアスについては Advanced Types に解説がある。
pub type Result<T, E = Report> = core::result::Result<T, E>;
Result
はScalaでのEither
みたいなものと思ってよいのかな(Scalaもよく知らないけれど)。
Result
を使ったエラーハンドリングについては Recoverable Errors with Result に解説がある。 return Err(e)
のようにエラーを返すとエラー出力させたりできるのだろうけれど、miseのmain関数では特にエラーを返してはおらず、eyre::Result<()>
も E
を含まないので慣例的に(あるいは将来の拡張として)エラーハンドリングを意識した実装にはなっているけれど、実際には未使用のような感じな気がする。
eyre::Result
についてもう少し深堀りする。
eyre
の部分は名前空間であろうから(参考: Non-operator Symbols)、eyre::
ということは外部ライブラリなどに定義があるのだろうと思われる。
より詳しい概念の説明があった。
外部ライブラリのような考え方は、だいたいcreteの単位で扱われると思ってよいのかな。
eyre
もcreteということでよいと解釈した。
Type Definition eyre::Resultには main
で使うコード例が記載されているがどう動くのかあまりイメージできないな…。
Struct eyre::Reportを読むと Box<dyn std::error::Error>
というキーワードが登場し、 Box<dyn error::Error>とは? を読むとエラーハンドリング時のError traitの型を動的に決定するための仕組みを示していそうなので、ここでいうラッパーの役割を担っているようにも思われる。
この実装は Rust By Example のBoxing errors にも記載があるけれど、実際どのように影響するのか現時点だと理解できていないので一旦後回しにする。
さて、 fn main()
に戻って処理を追ってみる。
ちなみに実装や仕様を確認するような場合にはこんなに1行ずつ細かく処理を追ったりはしないけれど、今回はRustの勉強をしたいのが主目的なのでじっくりやっている。
fn main() -> eyre::Result<()> {
output::get_time_diff("", ""); // throwaway call to initialize the timer
...
最初は output::get_time_diff("", "")
が呼び出されている。output.rs
に定義されており、 output::
の部分は他言語では namespace
などを示すことが多い気がするけれど、どうもそのような定義はなさそうにも読める。深堀りしていく。
まず呼び出し側では、コードの上部で以下のような宣言(?)が見られた。
#[macro_use]
mod output;
Modules の File hierarchy によると、 mod output;
は output.rs
の内容を output::
で呼び出せるような仕組みらしい。Rustにおいてはファイル単位でモジュールのスコープが分割されているようである。
同一ファイル内でモジュールを使うには mod xxx { ... }
のように括弧で囲う。こちらはなんとなく馴染みがある記法である。
モジュール内の関数などは関数宣言の前に pub
を付加することでモジュール外から呼び出せるようになる。 get_time_diff
も pub fn get_time_diff(module: &str, extra: &str)
と宣言されているので外部参照が可能になっていることが分かる。
この mod
は TypeScript
の import
と同じようなイメージで使えるのだろうと考えていたけれど、まさにそうではないという記事を読んでモジュールがどのような扱いになるのか理解できた気がする。クレートとモジュールも微妙に境界が分からず混乱していたので、分かりやすく解説されており大変に有り難い。
多少まだ誤解があるかもしれないけれど、今のところこのような理解をした。
- Rustにおいてはファイルパスに対応してモジュールの階層が切られ、
mod
はその階層からの相対指定でモジュールを読み込む -
src/main.rs
でmod
を宣言した場合はルートモジュールに読み込まれる- そして、読み込まれたモジュールは下位のモジュールでも参照することができる
- ここで
crate::mod1::hello();
のような記法になるのでクレートと混乱してしまう…
- クレートはビルドの単位であり、1クレートごとに1つのバイナリファイル(バイナリクレートまたはライブラリクレート)が生成される
最後に #[macro_use]
については、 The macro_use attribute などを読んだ限りでは mod output
の内部で #[macro_export]
ラベルが付けられたマクロ定義を読み込み側で参照可能にするもの…と解釈した。
補足として、マクロについては Macros に記載がある。基本的な考え方は一般的なマクロと概ね同じようだけれど、 procedural macros
はまだ読んでいないので後で理解する。
fn get_time_diff()
を読んでいく。
本題の前に #[cfg(feature = "timings")]
の記述について。
Features によると、これは所謂Feature Flagsで Cargo.toml
の [features]
セクションに定義があれば関数定義が有効になるという仕組みらしい。
呼び出される関数が未定義のままだとビルドエラーになるので、通常は #[cfg(not(feature = "timings"))]
のようにフラグが無効な場合の定義も併記するのだろう。get_time_diff
についても以下のような関数定義が存在する。
さて、 get_time_diff()
である。関数定義である get_time_diff(module: &str, extra: &str) -> String
の引数と戻り値はよいとして &str
という記述は他言語でも馴染のある 参照(Shared references) だった。str は文字列スライスを示す型で、文字列周りは奥が深いので詳細は別途整理する。