Rustで乱数を生成してみた記録です。また、その中で、学んだことを自分の理解のために、未来の自分に説明するのがこの記事の役割です。
本記事は、基本的に次の本に書いてある情報を理解するために、他の資料を参照しながら、記事で学べることを僕の言葉で説明したものになります。
数当てゲームのプログラミング - The Rust Programming Language 日本語版
また、自分の理解を補完したり、知的好奇心を満たすために、次の本やrandクレートのコードも眺めたりしました。
なお、僕はRust初めて3日目の人なので、普通に説明が間違っている可能性があります。間違いに気づいた場合は、指摘いただけるとありがたく思います。
乱数を生成する
とりあえずRustのコードを書いて乱数を生成してみます。 そして、乱数を生成するのは簡単ではないので、既存のRustコードを利用することにします。
クレートを利用する方針を決める
Rustでは、既存のRustコードを利用する際に、少なくとも2種類の方法があります。*1
1つは、モジュールを利用する方法です。もう1つは、クレートを利用する方法です。
これらの違いはコンパイルのタイミングです。モジュールを利用する場合、作成するRustコードと一緒にコンパイルすることになります。一方で、コンパイル済みのRustコードを利用することもできます。後者はクレートを利用する方法です。
ここでは、乱数を生成するために、クレートを利用します。
具体的にはこちらになります。
クレートを取得する
クレートの実体は、一つのファイルです。慣習的には lib
がファイル名の先頭につき、拡張子は、 .rlib
とするようです。
また、一般に公開されているクレート(外部クレートと呼びます)を読み込むこともできます。 外部クレートを読み込む際は、Cargo.tomlに依存ファイルとして、クレートを指定します。すると、cargoでビルドを行うタイミングで、依存ファイルをダウンロードしてリンクしてくれます。
ここでは、乱数を生成する外部クレートのrandを読み込みます。
[dependencies] rand = "0.7.3"
なお、Cargoでビルドすると、randクレートが依存するクレートを一緒にダウンロードしてくれます。なお、バージョンは現時点の最新版を指定してます。
クレートを読み込む
クレートは次のように読み込んで利用することができます。
extern crate rand; use rand::Rng; fn main() { let rand_num = rand::thread_rng().gen_range(1, 101); println!("The random number {}.", rand_num); }
これを実行すると以下のようになります。
The random number 32.
外部クレートおよびトレイトを読み込む
extern crate rand;
は、外部のクレートを読み込む行です。モジュールを読み込む際の use rand
に該当します。
use rand::Rang;
は、 rand::Rng
を読み込みます。rand::Rng
はトレイトと呼ばれます。トレイトについてはこれから説明しますが、トレイトを読み込まないと、
rand::thread_rng()
の返り値に対してメソッドを実行できなくなります。
詳細は後で調べてみましょう。
use rand::Rang;
しておくことで、 rand::thread_rng().gen_range
が呼び出せるようになります。なお、指定せずに実行すると次のようなエラーがでます。
error[E0599]: no method named `gen_range` found for struct `rand::prelude::ThreadRng` in the current scope --> src/main.rs:6:39 | 6 | let rand_num = rand::thread_rng().gen_range(1, 101); | ^^^^^^^^^ method not found in `rand::prelude::ThreadRng` | ::: /Users/mtb/.cargo/registry/src/github.com-1ecc6299db9ec823/rand-0.7.3/src/lib.rs:212:8 | 212 | fn gen_range<T: SampleUniform, B1, B2>(&mut self, low: B1, high: B2) -> T | --------- the method is available for `std::boxed::Box<rand::prelude::ThreadRng>` here | = help: items from traits can only be used if the trait is in scope help: the following trait is implemented but not in scope; perhaps add a `use` for it: | 5 | use crate::rand::Rng; | error: aborting due to previous error For more information about this error, try `rustc --explain E0599`. error: could not compile `myproject`. To learn more, run the command again with --verbose.
仕組みを理解する
ここからはRustの仕組みを理解する話です。
クレートを理解する
Rustでは、コンパイルの単位をクレートと呼びます。
例えば、rustc some_file.rs
が呼ばれると、 some_file.rs
はクレートファイルとして扱われます。
Rustのコードの中でモジュールをインポートしている場合、まとめてコンパイルされます。しかし、クレートが分かれていると独立してコンパイルされます。
Rustは、2つの形式にコンパイルできます。それは、バイナリ形式とライブラリの形式です。rustcのデフォルトは、バイナリのコンパイルします。 --crate-type
フラグにlibを指定すると、ライブラリ形式でコンパイルできます。
詳細は以下のページに説明が書いてあります。
crate.ioを知る
crate.ioはRustのcrateリポジトリです。
crates.io: Rust Package Registry
PythonでいうPyPIのようなものかな、と思っています。(PyPIはよく使っているので名前を出しましたが、他の言語でもリポジトリはあると思います)
とりあえず、GitHubアカウントでログインできるようです。
Cargoの依存ファイル管理について理解する
Cargoでビルドする際に、プロジェクトが依存しているクレートは自動でDLします。
その際に、DLしたクレートのバージョンを Cargo.lock
に記録されます。以下は該当ファイルの一部例です。
これにより、Cargoビルドは再現性を持つとのことです。
# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" [[package]] name = "getrandom" version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "libc" version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ...
なお cargo update
を実行すると依存ファイルは最新に更新されます。
トレイトとは何かをチラ見する
トレイトは、Javaのインターフェースに近いポリモーフィズムを実現するための仕組みのようです。こちらの書籍の中で関連情報が説明されています。
で、randクレートの中でトレイトが実際にどう実装されるのかが気になったので、randのコードを読んでみました。
リポジトリはこちら
GitHub - rust-random/rand: A Rust library for random number generation.
randのソースコードを見ると、rand::Rng
はここで定義されています。
pub trait Rng: RngCore {
rng::Rng
にあります。
また、 thread_rng
は以下で定義されています。
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] pub fn thread_rng() -> ThreadRng { let rng = THREAD_RNG_KEY.with(|t| t.clone()); ThreadRng { rng } }
rand/src/rngs/thread.rs at master · rust-random/rand · GitHub
ThreadRng自体は以下で定義されています。
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] #[derive(Clone, Debug)] pub struct ThreadRng { // Rc is explictly !Send and !Sync rng: Rc<UnsafeCell<ReseedingRng<Core, OsRng>>>, }
なるほど。「前提知識が足りなさすぎて、何もわからない」ということがわかりました。(ぐぬぬ)(そもそも #[]
は何だろう?Pythonでいうデコレータ的なものだろうか。
また、トレイトを読み込まなかったときに、ログメッセージ rand::prelude::ThreadRng
というキーワードがあったので、それっぽいファイルを覗いてみると、次のような定義がありました。
pub use crate::rngs::ThreadRng;
https://github.com/rust-random/rand/blob/master/src/prelude.rs#L29
まあ、なんとなく「 prelude
というファイルが準備ファイルで、これを読み込むといろんなトレイトを読み込んだりできるのかなー」とか、「トレイトを抽象化する仕組みがあるのかなー」とか想像したりしましたが、今のところはよくわかりませんでした。
そのうち、理解できたらいいなと思います。
終わり
今回は、外部クレートを読み込んで、利用してみました。 また、関連する知識について調べてみました。 少しだけRustについての理解が深まりました。
続いて、時間があるときに続きを学んでいきたいと思います。
*1:他にもあるような気がしますが、今のところ僕は知りません。