コンテンツにスキップ

Rustのエラー E0252: the name is defined multiple times(2つのuseで名前が衝突)

E0252 は、2つの use の行が同じ名前を持ち込んでいる、という意味のエラーです。お手本のような例が、use std::fmt::Result; と use std::io::Result; の組み合わせです。違う2つの型が、どちらも Result という名前なのです。ほかにも fmt::Error と io::Error、2つの Write トレイトなどがあります。とてもよく出会うエラーですが、直すのは簡単です。たいていは use の行を1つか2つ変えるだけで済みます。rustc 1.97 でも、このエラーは自分のコード E0252 で表示されます。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0252]: the name `Result` is defined multiple times
--> src/main.rs:2:5
|
1 | use std::fmt::Result;
| ---------------- previous import of the type `Result` here
2 | use std::io::Result; // ❌ a second `Result`
| ^^^^^^^^^^^^^^^ `Result` reimported here
|
= note: `Result` must be defined only once in the type namespace of this module
help: you can use `as` to change the binding name of the import
|
2 | use std::io::Result as OtherResult; // ❌ a second `Result`
| ++++++++++++++

「the name Result is defined multiple times」は「Result という名前が複数回定義されている」という意味です。

このエラーが出るのは、次のプログラムです。

use std::fmt::Result;
use std::io::Result; // ❌ a second `Result`
struct Temperature {
celsius: i32,
}
impl std::fmt::Display for Temperature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> Result<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(std::io::Error::other("not a number")),
}
}
fn main() {
match parse_temperature("21") {
Ok(t) => println!("{}", t),
Err(e) => println!("error: {}", e),
}
}

コメントは「2つ目の Result」という意味です(エラーになる例なので、コードはそのままにしています)。

rustc はこのあとにさらに4つのエラーを表示します。E0107(「type alias takes 0 generic arguments」、型エイリアスはジェネリック引数を0個取る)、2つの E0308(型の不一致)、そして E0277 です。これらを追いかける必要はありません。Rust は1つ目のインポートを残したので、ファイルの中の Result はすべて fmt::Result の意味になり、parse_temperature のつじつまが合わなくなっただけです。E0252 を直せば、全部消えます。

rustc は2つの use の行を指しています。

  1. --- の行(1行目):「previous import of the type Result here」(型 Result の前のインポートはここ)。こちらが先に来たので、Rust が残すのはこちらです。
  2. ^^^ の行(2行目):「Result reimported here」(Result がここで再びインポートされた)。2つ目のインポートが、同じ名前を使おうとしています。
  3. help: は、2つ目のインポートの名前を as で変えることを提案しています。

std::fmt::Result と std::io::Result は、本当に別のものです。fmt::Result はただの Result<(), fmt::Error> で、Display や Debug が使います。io::Result<T> は Result<T, io::Error> で、ファイルやネットワークが使います。名前は同じでも、仕事が違うのです。

スマホの連絡先に、自分の母と、パートナーの母の2人を「お母さん」で登録するようなものです。「お母さん」をタップしたとき、スマホはどちらにかければいいのかを知る必要があります。だから片方に言葉を足すか、「お母さん(アナ)」と「お母さん(リー)」のように登録します。

1つのモジュールの中では、どの名前もちょうど1つのアイテムを指します。use の行は何かをコピーするわけではなく、別の場所にあるアイテムに短い名前を付け足すだけです。2つの use の行が同じ短い名前を付け足すと、そのあとの Result がすべてあいまいになってしまいます。だから Rust は、使う場所ごとに推測するのではなく、インポートのところで止まるのです。

E0252 は、同じ見出しを持つ3兄弟の1つです。E0255 は use の行と自分のアイテムの衝突、E0428 は自分のアイテム2つの衝突です。3つとも、根っこにあるルールは「1つのモジュールでは、1つの名前に1つのもの」です。モジュールも見てください。

直し方1:モジュールをインポートして、fmt:: と io:: を書く

Section titled “直し方1:モジュールをインポートして、fmt:: と io:: を書く”

たいていはこれを選びます。Result と Error なら、いつでもこれです。親のモジュールをインポートして、前にモジュール名を書きます。これで、どの Result なのかが、使うたびにわかります。Rust の本(The Rust Book)も、まさにこの場合にこのスタイルを勧めています。

use std::fmt;
use std::io;
struct Temperature {
celsius: i32,
}
impl fmt::Display for Temperature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> io::Result<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(io::Error::other("数値ではありません")),
}
}
fn main() {
for input in ["21", " -4 ", "warm"] {
match parse_temperature(input) {
Ok(t) => println!("{}", t),
Err(e) => println!("エラー: {}", e),
}
}
}

実行結果:

21 °C
-4 °C
エラー: 数値ではありません
▶ 実際に試してみよう

おまけ:プレリュード(prelude)にあるふつうの Result<T, E> は、インポートしなくても、これまでどおり自由に使えます。

直し方2:片方のインポートの名前を as で変える

Section titled “直し方2:片方のインポートの名前を as で変える”

2つの型のうち片方をたくさん使っていて、短い名前を付けたいときは、これを選びます。rustc が提案しているのもこの方法です。OtherResult ではなく、IoResult のように、どこから来たのかがわかる名前を選びましょう。

use std::fmt::Result;
use std::io::Result as IoResult;
struct Temperature {
celsius: i32,
}
impl std::fmt::Display for Temperature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> IoResult<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(std::io::Error::other("数値ではありません")),
}
}
fn main() {
match parse_temperature("21") {
Ok(t) => println!("{}", t),
Err(e) => println!("エラー: {}", e),
}
}

実行結果:

21 °C
▶ 実際に試してみよう

ただの Result がこっそり fmt::Result を意味していると、次にコードを読む人が驚くかもしれません。なので、たいていは直し方1のほうがわかりやすくなります。as が活躍するのは、use std::collections::BTreeMap as Map; のような長い名前のときです。

直し方3:トレイトを as _ でインポートする

Section titled “直し方3:トレイトを as _ でインポートする”

メソッドを呼ぶためだけにインポートするトレイトなら、これを選びます。お決まりの例が2つの Write トレイトです。fmt::Write を使うと String に write! でき、io::Write を使うと標準出力やファイルに write! できます。両方を名前付きでインポートすると、ぶつかります。

error[E0252]: the name `Write` is defined multiple times
--> src/main.rs:2:5
|
1 | use std::fmt::Write;
| --------------- previous import of the trait `Write` here
2 | use std::io::Write; // ❌ a second `Write`
| ^^^^^^^^^^^^^^ `Write` reimported here
|
= note: `Write` must be defined only once in the type namespace of this module
help: you can use `as` to change the binding name of the import
|
2 | use std::io::Write as OtherWrite; // ❌ a second `Write`
| +++++++++++++

今度は「previous import of the trait Write here」(トレイト Write の前のインポートはここ)と表示されています。エラーになるプログラムはこちらです。

use std::fmt::Write;
use std::io::Write; // ❌ a second `Write`
fn main() {
let mut report = String::new();
write!(report, "{} tests passed", 12).unwrap();
let mut out = std::io::stdout();
writeln!(out, "{}", report).unwrap();
}

ここでは、自分で Write と書く場所はありません。必要なのはメソッドだけです。as _ を使うと、トレイトを名前を付けずにインポートできます。だから何もぶつからず、両方のメソッドがちゃんと使えます。

use std::fmt::Write as _;
use std::io::Write as _;
fn main() {
let mut report = String::new();
write!(report, "{} 件のテストに合格しました", 12).unwrap();
let mut out = std::io::stdout();
writeln!(out, "{}", report).unwrap();
}

実行結果:

12 件のテストに合格しました
▶ 実際に試してみよう

たとえば fn log(w: &mut impl io::Write) のように、どちらかに名前が必要なら、直し方1と組み合わせます。use std::io; と書いて、io::Write を使いましょう。トレイトも見てください。

直し方4:同じアイテムの重複したインポートを消す

Section titled “直し方4:同じアイテムの重複したインポートを消す”

2つの行がまったく同じものをインポートしているときは、これを選びます。コードをマージしたあとや、すでにあるインポートをエディタが追加したときによく起こります。

error[E0252]: the name `HashMap` is defined multiple times
--> src/main.rs:2:24
|
1 | use std::collections::HashMap;
| ------------------------- previous import of the type `HashMap` here
2 | use std::collections::{HashMap, HashSet}; // ❌ `HashMap` again
| ^^^^^^^--
| |
| `HashMap` reimported here
| help: remove unnecessary import

エラーになるプログラムはこちらです(コメントは「また HashMap」という意味です)。

use std::collections::HashMap;
use std::collections::{HashMap, HashSet}; // ❌ `HashMap` again
fn main() {
let mut stock: HashMap<&str, u32> = HashMap::new();
stock.insert("apples", 3);
let mut sold_out: HashSet<&str> = HashSet::new();
sold_out.insert("pears");
println!("apples: {}, sold out: {}", stock["apples"], sold_out.len());
}

help が違うことに気づきましたか? as はなく、「remove unnecessary import」(不要なインポートを消す)とだけ書かれています。1行だけ残しましょう。

use std::collections::{HashMap, HashSet};
fn main() {
let mut stock: HashMap<&str, u32> = HashMap::new();
stock.insert("apples", 3);
let mut sold_out: HashSet<&str> = HashSet::new();
sold_out.insert("pears");
println!("りんご: {}、売り切れ: {}", stock["apples"], sold_out.len());
}

実行結果:

りんご: 3、売り切れ: 1
▶ 実際に試してみよう

RustのHashMapとHashSetとBTreeMapも見てください。

2か所から来る Error。 use std::fmt::Error; と use std::io::Error;、あるいは use std::error::Error; と use anyhow::Error; の組み合わせです。直し方:use std::io; と書いて io::Error を使うか、use anyhow::Error as AnyError; とします。anyhowとthiserrorも見てください。

同じ型の名前を持つ2つのクレート。 use chrono::Duration; と use std::time::Duration; は、日付を扱うプロジェクトでよく見る組み合わせです。直し方:片方は短い名前のまま使い、もう片方は chrono::Duration のようにモジュール名を付けて書きます。日付と時刻も見てください。

グロブインポート。 use std::fmt::*; と use std::io::*; を並べても、use の行では E0252 になりません。問題が起こるのはあとで、最初の Result のところで、コードなしのエラー error: `Result` is ambiguous(Result があいまい)が出ます。直し方:直し方1のように、必要な名前を明示的にインポートします。

  • E0255:use の行が、同じ名前の自分の構造体、列挙型、関数とぶつかる。
  • E0428:自分のアイテム2つが同じ名前を使っている。関数をオーバーロードしようとしたときによく起こります。
  • E0432:解決できないインポート。use の行が、存在しないものを指している。
  • E0034:スコープにある2つのトレイトが、型に同じ名前のメソッドを与えている。
  • E0308:型の不一致。Result がぶつかったあとに続けて出るエラーの1つです。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. 同じファイルで fmt::Result と io::Result が必要です。ふつう選ぶ、すっきりしたインポートはどれですか?

2. std::fmt::Write と std::io::Write の両方のメソッドが必要ですが、トレイトの名前を自分で書くことはありません。どうすればいいですか?

3. rustc が HashMap is reimported here と表示し、remove unnecessary import を提案しました。何が起きたのでしょうか?

スコア: 0 / 3