この話には、可視なトレイトのみ使用できるという言語仕様と、常用されるアイテム(型・トレイト・関数等)をモジュールprelude
で再エクスポートするという慣習の2つが関わります。
可視なトレイトのみ使用できる
まず、言語仕様として、可視なトレイトのみ使用できるという点です。
トレイトはJavaのinterfaceなどに似た、様々な型に共通の振る舞いを抽出して抽象的に定義する仕組みです。
(参照: https://doc.rust-jp.rs/book-ja/ch10-02-traits.html )
トレイトという仕組みでは、1つのトレイトに対して複数の型がそのトレイトを実装することができますし、1つの型が複数のトレイトを実装することもできます。
また、トレイトの実装を書ける場所は
- 型を宣言したモジュールで、外部のトレイトに対して実装を書く
- トレイトを宣言したモジュールで、外部の型に対して実装を書く
という2通りがあります。
ここで問題になるのが、後者の外部の型に対して実装が書かれていて、メソッド呼び出しをしたとき、メソッドを探す範囲をどこまでにするかという問題です。
以下のような例を考えてみましょう。
mod types {
pub struct ValueType {
}
impl ValueType {
pub fn new() -> ValueType {
ValueType{}
}
}
}
mod alpha {
use crate::types::ValueType;
pub trait Alpha {
fn foo(&self) -> usize;
}
impl Alpha for ValueType {
fn foo(&self) -> usize {
0
}
}
}
mod beta {
use crate::types::ValueType;
pub trait Beta {
fn foo(&self) -> &str;
}
impl Beta for ValueType {
fn foo(&self) -> &str {
"fooooo!"
}
}
}
use crate::types::ValueType;
fn main() {
let val = ValueType::new();
println!("val.foo() = {}", val.foo()); // これを Alpha::foo() とみなすか、Beta::foo とみなすかが判定できない。
}
上記のコードをコンパイルすると以下のようにエラーが出ます。
error[E0599]: no method named `foo` found for struct `ValueType` in the current scope
--> src/main.rs:45:36
|
2 | pub struct ValueType {
| -------------------- method `foo` not found for this
...
45 | println!("val.foo() = {}", val.foo()); // これを Alpha::foo() とみなすか、Beta::foo とみなすかが判定できない。
| ^^^ method not found in `ValueType`
|
= help: items from traits can only be used if the trait is in scope
help: the following traits are implemented but not in scope; perhaps add a `use` for one of them:
|
40 | use crate::alpha::Alpha;
|
40 | use crate::beta::Beta;
|
For more information about this error, try `rustc --explain E0599`.
error: could not compile `playground` due to previous error
これは、コンパイラがfn main()
内から見えている範囲で探す限り、 .foo()
というメソッドが見つからなかったというエラーです。
これを解決するには、コードにトレイトのインポートを含めて以下のようにします。
use crate::types::ValueType;
use crate::alpha::Alpha;
fn main() {
let val = ValueType::new();
println!("val.foo() = {}", val.foo()); // 間違いなく Alpha::foo に確定する。
}
なぜこのような仕様になっているかというと、例のようにトレイトを定義した場所で既存の型にトレイト実装が追加された場合、どのトレイトを使用しているかが分からなくならないようにするためです。
また、暗黙的に探すようにすると、メソッド1つを解決するために使っているクレートすべての全てのモジュールを探さなければならず、コンパイラの負荷もかなり高くなってしまいます。
こういった問題を回避するために、トレイトのメソッドを使う場合は必ずuseしてインポートする必要があります。
質問の例では、f.read_to_string(&mut contents)
がstd::io::Read::read_to_string
の呼び出しであるため、std::io::Read
のインポートが必要です。
prelude モジュールの慣習
2つめとして、prelude
モジュールの慣習があります。
質問例のコードではstd::io::Read
を直接指定してインポートしておらず、std::io::prelude
を介してインポートしています。
Rustでは、あるモジュールやクレートを使うためにたくさんのアイテム(型・関数・トレイト・定数 など)をインポートしなければならない場合、prelude
というモジュールで再エクスポート(pub use super::Foo;
など)しておき、使用側ではuse some_module::prelude::*;
で全てインポートするという慣習があります。
これによって、わざわざ個別にインポートしなくてもトレイトを使用できます。
一見するとuse some_module::*;
でもよさそうに見えますが、モジュール内には実装の都合上pub
であるものの、日頃は外部から使わないようなアイテムや、いろいろなモジュールに同名のものが存在するアイテムが存在します。
そういったものを一緒にインポートしてしまうと面倒なので、モジュールの提供側でprelude
に整理して提供しています。
std::io
もstd::io::prelude
でよく使用されるアイテムを再エクスポートしています。
ですから、use std::io::prelude::*;
でstd::io::Read
をインポートすることができています。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。