matobaの備忘録

和歌山と東京を往復しつつ活動するエンジニアの記録

Rustで乱数を生成してみる

Rustで乱数を生成してみた記録です。また、その中で、学んだことを自分の理解のために、未来の自分に説明するのがこの記事の役割です。

本記事は、基本的に次の本に書いてある情報を理解するために、他の資料を参照しながら、記事で学べることを僕の言葉で説明したものになります。

数当てゲームのプログラミング - The Rust Programming Language 日本語版

また、自分の理解を補完したり、知的好奇心を満たすために、次の本やrandクレートのコードも眺めたりしました。

github.com

なお、僕はRust初めて3日目の人なので、普通に説明が間違っている可能性があります。間違いに気づいた場合は、指摘いただけるとありがたく思います。

乱数を生成する

とりあえずRustのコードを書いて乱数を生成してみます。 そして、乱数を生成するのは簡単ではないので、既存のRustコードを利用することにします。

クレートを利用する方針を決める

Rustでは、既存のRustコードを利用する際に、少なくとも2種類の方法があります。*1

1つは、モジュールを利用する方法です。もう1つは、クレートを利用する方法です。

これらの違いはコンパイルのタイミングです。モジュールを利用する場合、作成するRustコードと一緒にコンパイルすることになります。一方で、コンパイル済みのRustコードを利用することもできます。後者はクレートを利用する方法です。

ここでは、乱数を生成するために、クレートを利用します。

具体的にはこちらになります。

https://crates.io/crates/rand

クレートを取得する

クレートの実体は、一つのファイルです。慣習的には 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を指定すると、ライブラリ形式でコンパイルできます。

詳細は以下のページに説明が書いてあります。

クレート - Rust By Example 日本語版

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のインターフェースに近いポリモーフィズムを実現するための仕組みのようです。こちらの書籍の中で関連情報が説明されています。

実践Rustプログラミング入門

で、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:他にもあるような気がしますが、今のところ僕は知りません。