背景
最近はあまりに暇すぎてRustのtutorialを読んでいます.現在10章ぐらいまで読んでいるのですが,個人的に一番?になるのは,2. Programming a Guessing Gameのuse rand::Rng
ではないかなと思います.moduleあたりの内容を学んだあとにこのuse文を振り返ると,なぜこれが必須なのか疑問になります.それを理解するためにrand crateをあさっていました.結局その疑問に対しては,そういう仕様という理解なのですが,crate内の実装がtutorialの理解に役立つかなと思ったので,ちょっと自分のとった行動を書いてみます.Rust tutorialを読んでいる初心者の方に参考にしていただけたら嬉しいです.
注意
tutorial内のrand crateはversion0.8.5です.最新版の実装とは異なる可能性があります.
本文
Guessing gameで乱数を発生するために,以下の実装をします.
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
なぜuse rand::Rng
が必要なのかというと,gen_rangeがトレイトに実装されているからです.ただ,これは7. Managing Growing Projects with Packages, Crates, and Modulesを読んでいると,不思議です.structやenum,それに付随するmethodは,公開されていれば,useしなくても使用することができます.
例えば,rand::thread_rng()
はThreadRngのinstanceを作成しますが,必ずしもこれを経由する必要はありません.以下のコードも,同様にThreadRngのinstanceを作成できます(呼び出し方によって生成されるものの設定が異なる可能性はありますが).
let mut thread_rng = rand::rngs::ThreadRng::default();
では,少し実装を追ってみましょう.rand crateのrootは以下のようになっています.thread_rng
はここでuseされているので,rand::thread_rng()
で使用できるのですね.
// Re-exports from rand_core
pub use rand_core::{CryptoRng, Error, RngCore, SeedableRng};
// Public modules
pub mod distributions;
pub mod prelude;
mod rng;
pub mod rngs;
pub mod seq;
// Public exports
#[cfg(all(feature = "std", feature = "std_rng"))]
pub use crate::rngs::thread::thread_rng;
pub use rng::{Fill, Rng};
#[cfg(all(feature = "std", feature = "std_rng"))]
use crate::distributions::{Distribution, Standard};
ただ,ここでは,ThreadRngがuseされていません.なので,一見するとThreadRngのinstanceを生成できるのは変です.ただ,実装をよく追うと,ThreadRngが公開されていることがわかります.
#[cfg(all(feature = "std", feature = "std_rng"))] pub use self::thread::ThreadRng;
mod.rsを用いるのは,7.5節にあるように,古い習慣です.上のlib.rsでrngs moduleが公開され,さらにrngs内でThreadRngも公開されています.
impl Default for ThreadRng {
fn default() -> ThreadRng {
crate::prelude::thread_rng()
}
}
thread.rsでdefaultが実装されており,これでinstanceを作成できます.では,RngがThreadRngに実装されているのかと思い,このファイルの中を捜索してもRngは実装されていません.代わりに,RngCore traitが実装されています.
impl RngCore for ThreadRng {
#[inline(always)]
fn next_u32(&mut self) -> u32 {
// SAFETY: We must make sure to stop using `rng` before anyone else
実は,ThreadRngにRngが実装されているかどうかを確認するためには,Rngを確認する必要があります.
pub trait Rng: RngCore {
/// Return a random value supporting the [`Standard`] distribution.
///
これはtutorialに(少なくとも10章までは)書かれていないのですが,RngはRngCoreを継承しています.ですが,これだけではThreadRngがRngを実装している根拠にはなりません.根拠になるのは,同ファイルの以下の実装です.これは,10.2節のblanket implementationです.
impl<R: RngCore + ?Sized> Rng for R {}
これによって,RngCoreを実装するThreadRngにも,自動でRngが実装されるのです.
では結局,traitのmethodを使用するには,useしなければならないのでしょうか.これは,よくわかりませんでした.一応,tutorial内でも,10.2節で,次のように記述されています.なので,そういう仕様だと理解しています.
The only difference is that the user must bring the trait into scope as well as the types.
structはscopeに必ずしも入れなくてもよいので,as well asは変ですが,traitは必ずscopeに入れる必要があります.
まとめ
今回,traitがstruct等とルールが異なることを不思議に思い,rand crate内を見て回っていました.結局,扱いが違う以上のことはわかっていないのですが,コードを追う過程で,tutorial内の知識がかなり活用されました.tutorialを読んでいる方は,実装を追ってみると勉強になるかと思います.今回の記事がその助けになれば幸いです.