TL;DR

  • ワイルドカードインポート(use crate_name::*;)を使うと、外部クレートの破壊的でない変更でも、ローカルの名前と衝突してコードが壊れることがある。
  • 自分で制御できないクレートからのワイルドカードインポートは避ける。例外は、内部モジュールの再エクスポートと prelude である。

内容

https://effective-rust.com/wildcard.html

名前の衝突

ワイルドカードインポートした名前は優先度が低いので、通常はローカルの名前が優先される。

use bytes::*;
 
struct Bytes(Vec<u8>);

しかし、外部クレートに追加されたメソッドの名前が、ローカルで定義したメソッドの名前と衝突することがある。たとえば、自分で次のトレイトを定義していたとする。

trait BytesLeft {
    fn remaining(&self) -> usize;
}
 
impl BytesLeft for &[u8] {
    fn remaining(&self) -> usize {
        self.len()
    }
}

ワイルドカードでインポートしている外部クレートに、同じ名前のメソッドが追加されると、コンパイラはどちらのメソッドを呼ぶのか判断できず、コンパイルエラーになる。

let arr = [1u8, 2u8, 3u8];
let v = &arr[1..];
assert_eq!(v.remaining(), 2);

自分で制御できないクレートからのワイルドカードインポートは避けるべき

例外

次の場合はワイルドカードインポートを使ってよい。

  • コードを分割するためにモジュールを使っていて、内部モジュールの公開アイテムを再エクスポートする場合(mod thing; pub use thing::*;)
  • 外部クレートが prelude モジュールを提供している場合。prelude はワイルドカードでインポートされることを前提に作られている。上の問題は起こりうるが、アイテムが慎重に選ばれているので、利便性がリスクを上回る

また、ワイルドカードインポートする依存ライブラリのバージョンを固定すれば、この問題は避けられる(項目21)。

キーワード

自分の理解

どのアイテムがどのクレート由来なのか分からなくなるので、個人的にはワイルドカードインポートはしないでほしい。特に Rust は依存クレートが多くなりやすい。