Rustで use std; が必要なときとエラーになるときがあるのは何故か
Rustでは use std;
や use rand;
のようなインポートが必要な場合と、逆に書くとエラーになる場合がある。
簡単に言うと
- トップレベルモジュールでは、書くとエラーになる(既にある名前と衝突する)。それ以外の場所では、必要な場合がある。
- これは名前をローカルで使うための仕組みと、名前の別名を公開するための仕組みが同じ
use
により実現されていることと関係している。
そもそも use std;
は何故必要なのか
例えば、あるモジュール m1.rs
内で以下のようなコードを書くとエラーになる。
fn foo() { let stderr = std::io::stderr(); }
rustc 1.18.0 (03fc9d622 2017-06-06) error[E0433]: failed to resolve. Use of undeclared type or module `std` --> m1.rs:2:18 | 2 | let stderr = std::io::stderr(); | ^^^^^^^^^^^^^^^ Use of undeclared type or module `std` error: aborting due to previous error
呼ぼうとしているのは確かに std::io::stderr
なのにこれが発見されないのは何故か。これは、この手のパスが文脈によって異なる解釈をされることに由来する。そもそもパス(::
で区切られているやつ)は次の3種類に分けられる。
::
または$crate::
で始まるもの (例:::std::default::Default
)- これらは絶対パスとして解釈される。
self
またはsuper
で始まるもの (例:self::MyTrait
) (※単独のself
は例外)- これらは相対パスとして解釈される。
- それ以外
つまり、 std::io::stderr
はここでは絶対パスではなく、相対パスのようなものとして扱われてしまっているのである。
絶対パスとして修正する場合
先ほどのコードで意図しているのは絶対パスだと考えられる。したがって次のようにすればエラーにはならない。
fn foo() { let stderr = ::std::io::stderr(); }
レキシカルスコープのパスとして修正する場合
もう一つの解釈として、レキシカルスコープのパスとしての使用を意図していた可能性も考えられる。例えば、以下のコードは正しい。
use std::io; fn foo() { let stderr = io::stderr(); }
同じ要領で、 std
をインポートすればよいという考えがありえる。これも正しい。
use std; fn foo() { let stderr = std::io::stderr(); }
トップレベルモジュールでは不要なのはなぜか
トップレベルモジュールでは通常、 extern crate
が多く行われている。例えば、 extern crate rand;
と書いておくと、 rand
のトップレベルモジュールが ::rand
にリンクされる。
ここでもし m1
モジュール内で use rand;
をすると、これがさらに ::m1::rand
にリンクされることになる。これにより m1
の直下で rand
を簡単に参照できるようになる。
同じ理屈で、トップレベルモジュールで use rand;
をすると、 ::rand
を ::rand
にリンクしようとしていることになる。これはおかしいのでエラーになってしまうという寸法である。
こういった理屈のため、 use nalgebra as na;
のような別名インポートはトップレベルモジュールでも必要である。例えば、
extern crate nalgebra; use nalgebra as na; .. mod m1 { use nalgebra as na; }
のようになる。もちろん、以下のようにそもそも extern crate
の時点で別名をつけてリンクすることもできる。(好ましい習慣かは別として)
extern crate nalgebra as na; // use 不要 mod m1 { use na; }
Rustの use
の設計について
Rustの use
は2つの目的を兼ねている。これは例えばこのNiko Matsakis氏のコメントでも確認できる。
- ある定義(関数や型など)に別名をつける(別名をつけて公開する)こと。 (再エクスポート)
- ある定義をローカルから使うためにスコープに入れること。 (インポート)
前者はモジュールグラフの切り貼り自体を目的にしているのに対して、後者は let
などと同様にレキシカルスコープへの名前の導入ができればよく、グラフの切り貼りは意図していない。しかしRustでは両者を同じように、グラフの切り貼りで実現する仕組みになっている。このことは、 use
の動作を理解するにあたって押さえておくとよいところかもしれない。
まとめ
- トップレベルモジュールでは、
use std;
を書くとエラーになる(既にある名前と衝突する)。それ以外の場所では、use std;
が必要な場合がある。 - これは名前をローカルで使うための仕組みと、名前の別名を公開するための仕組みが同じ
use
により実現されていることと関係している。