コンテンツにスキップ

Rustのエラー E0255: the name is defined multiple times(useと自分の型の名前が衝突)

E0255 は、use の行と、自分で定義したものが同じ名前になっている、という意味のエラーです。よくあるのは、ファイルの先頭で std::io::Error をインポートしておきながら、あとで自分の enum Error を書いてしまう場合です。Rust では1つの名前に2つの意味を持たせられないので、コンパイルが止まります。自分でエラー型を書き始めたころにとてもよく出会うエラーですが、直すのは短い1行で済みます。E0412 と違って、このコードは最近のリリースでも変わっていません。rustc 1.97 でも E0255 として表示されます。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0255]: the name `Error` is defined multiple times
--> src/main.rs:3:1
|
1 | use std::io::Error;
| -------------- previous import of the type `Error` here
2 |
3 | enum Error { // ❌ same name as the import on line 1
| ^^^^^^^^^^ `Error` redefined here
|
= note: `Error` 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
|
1 | use std::io::Error as OtherError;
| +++++++++++++
warning: unused import: `std::io::Error`
--> src/main.rs:1:5
|
1 | use std::io::Error;
| ^^^^^^^^^^^^^^

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

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

use std::io::Error;
enum Error { // ❌ same name as the import on line 1
EmptyName,
TooLong(usize),
}
fn check_name(name: &str) -> Result<(), Error> {
if name.is_empty() {
return Err(Error::EmptyName);
}
if name.len() > 10 {
return Err(Error::TooLong(name.len()));
}
Ok(())
}
fn main() {
match check_name("") {
Ok(()) => println!("name is fine"),
Err(Error::EmptyName) => println!("name is empty"),
Err(Error::TooLong(n)) => println!("name has {} letters, max is 10", n),
}
}

コメントは「1行目のインポートと同じ名前」という意味です(エラーになる例なので、コードはそのままにしています)。

rustc は3つのことを示しています。

  1. --- の行(1行目):「previous import of the type Error here」(型 Error の前のインポートはここ)。use の行が、このモジュールにすでに Error という名前を入れています。
  2. ^^^ の行(3行目):「Error redefined here」(Error がここで再定義された)。自分の enum Error が、同じ名前を使おうとしています。
  3. help: は抜け道を教えてくれます。as を使って、インポートに別のローカルな名前を付ける方法です。

その下の warning: unused import(使われていないインポート)は、役に立つヒントです。コードの中で std::io::Error を実際には使っていない、という意味です(このプログラムの Error は、どれも自分の列挙型のことです)。その場合は、1行目を消すだけで済みます。両方とも必要なら、このまま読み進めてください。

注記(note)には 型の名前空間(type namespace)が出てきます。型、トレイト、モジュールは1つの名前のリストを共有し、関数、定数、static は別のリストを共有しています。だから、use std::mem::swap; と自分の fn swap を並べても同じエラーになります。ただし、そのときは「値の名前空間」(value namespace)です。

クラスに「アレックス」という生徒が2人いるところを想像してみてください。先生はもう「アレックス」とだけ呼ぶことはできません。どちらかに「アレックスB」のようなあだ名が必要です。インポートにそのあだ名を付けるのが as です。

Rust では、モジュールの中の1つの名前は、ちょうど1つのアイテムを指します。10行目の Error::EmptyName を読むとき、Rust はどの Error のことなのかを知る必要があり、推測はしません。黙って自分の列挙型を選べば use の行は何の意味もなくなりますし、io::Error を選べば自分の列挙型が使えなくなります。どちらにしても、黙って選ぶと間違いが隠れてしまいます。

だから E0255 は、E0428(自分のアイテム2つが同じ名前)と E0252(2つの use の行が同じ名前)の兄弟分です。ルールは同じで、「1つのモジュールでは、1つの名前に1つのもの」です。use の仕組みはモジュールを見てください。

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

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

同じファイルで両方の型が必要なときは、これを選びます。これはよくある状況です。アプリのルールには自分の Error を、ファイルやネットワークには io::Error を使う、という場合です。インポートには、どこから来たのかがわかるあだ名を付けましょう。rustc が仮に付けた OtherError より、IoError のほうが読みやすくなります。

use std::io::Error as IoError;
use std::io::ErrorKind;
#[derive(Debug)]
enum Error {
EmptyName,
TooLong(usize),
}
fn check_name(name: &str) -> Result<(), Error> {
if name.is_empty() {
return Err(Error::EmptyName);
}
if name.len() > 10 {
return Err(Error::TooLong(name.len()));
}
Ok(())
}
fn save_name(name: &str) -> Result<(), IoError> {
if name == "root" {
return Err(IoError::new(ErrorKind::PermissionDenied, "その名前は予約されています"));
}
Ok(())
}
fn main() {
println!("{:?}", check_name("Ferris"));
println!("{:?}", check_name(""));
println!("{:?}", check_name("Ferris the Crab"));
match save_name("root") {
Ok(()) => println!("保存しました"),
Err(e) => println!("I/Oエラー: {}", e),
}
}

実行結果:

Ok(())
Err(EmptyName)
Err(TooLong(15))
I/Oエラー: その名前は予約されています
▶ 実際に試してみよう

名前の長さ(name.len())を調べているので、check_name に渡す名前は英語のままにしています。日本語の文字は1文字で3バイトになり、結果が変わってしまうからです。

as が名前を変えるのはこのファイルの中だけです。ほかの場所では、この型は相変わらず std::io::Error です。

直し方2:型ではなくモジュールをインポートする

Section titled “直し方2:型ではなくモジュールをインポートする”

それぞれの Error を使うたびに、どこから来たのかを見せたいときは、これを選びます。親のモジュールを use std::io; でインポートして、io::Error と書きます。自分の Error は短い名前のまま使えます。Error や Result のようなありふれた名前の型では、このスタイルを好む Rust プログラマーがたくさんいます。

use std::io;
enum Error {
EmptyName,
}
fn check_name(name: &str) -> Result<(), Error> {
if name.is_empty() {
return Err(Error::EmptyName);
}
Ok(())
}
fn save_name(name: &str) -> Result<(), io::Error> {
if name == "root" {
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "予約された名前です"));
}
Ok(())
}
fn main() {
if let Err(Error::EmptyName) = check_name("") {
println!("名前が空です");
}
if let Err(e) = save_name("root") {
println!("I/Oエラー: {}", e);
}
}

実行結果:

名前が空です
I/Oエラー: 予約された名前です
▶ 実際に試してみよう

直し方3:自分のアイテムの名前を変える

Section titled “直し方3:自分のアイテムの名前を変える”

自分の型をたくさんのファイルで使うときや、ほかの人にも使ってもらうときは、これを選びます。2つ目の Error より、NameError や AppError のような具体的な名前のほうがわかりやすく、use の行もそのままにできます。

use std::io::Error;
#[derive(Debug)]
enum NameError {
EmptyName,
TooLong(usize),
}
fn check_name(name: &str) -> Result<(), NameError> {
if name.is_empty() {
return Err(NameError::EmptyName);
}
if name.len() > 10 {
return Err(NameError::TooLong(name.len()));
}
Ok(())
}
fn main() {
println!("{:?}", check_name("Ferris the Crab"));
let disk = Error::other("ディスクがいっぱいです");
println!("{}", disk);
}

実行結果:

Err(TooLong(15))
ディスクがいっぱいです
▶ 実際に試してみよう

大きなプロジェクトでは、thiserror クレートを使うと、こうしたエラーの列挙型をずっと短く書けます。独自のエラー型も見てください。

直し方4:使っていないインポートを消す

Section titled “直し方4:使っていないインポートを消す”

上のエラーのように、rustc が warning: unused import も表示しているときは、これを選びます。そのインポートは前のコードの残りか、エディタが勝手に追加したものです。その行を消せば、このページの最初のプログラムはそのまま動きます。

enum Error {
EmptyName,
TooLong(usize),
}
fn check_name(name: &str) -> Result<(), Error> {
if name.is_empty() {
return Err(Error::EmptyName);
}
if name.len() > 10 {
return Err(Error::TooLong(name.len()));
}
Ok(())
}
fn main() {
match check_name("") {
Ok(()) => println!("名前は問題ありません"),
Err(Error::EmptyName) => println!("名前が空です"),
Err(Error::TooLong(n)) => println!("名前が {} 文字あります(最大は10文字)", n),
}
}

実行結果:

名前が空です
▶ 実際に試してみよう

標準ライブラリと同じ名前の自分の関数。 use std::mem::swap; と fn swap(a: &mut i32, b: &mut i32) を並べると、「previous import of the value swap here」(値 swap の前のインポートはここ)と「value namespace」(値の名前空間)の注記付きで E0255 になります。直し方:自分の関数を swap_values と呼ぶか、自分で書くつもりだったならインポートを消します。

モジュール間のコピー&ペースト。 struct User を mod models に移して、先頭に use models::User; を追加したのに、main.rs の古い struct User を消し忘れた場合です。直し方:古いコピーを消して、User が models の中のものだけを指すようにします。

標準ライブラリと同じ名前のトレイト。 use std::fmt::Display; と自分の trait Display も、同じようにぶつかります(「previous import of the trait Display」、トレイト Display の前のインポート)。直し方:自分のトレイトの名前を(Describe や Render に)変えるか、use std::fmt; と書いて fmt::Display を使います。

覚えておくと便利なこと:use std::io::*; のようなグロブ(glob)インポートでは、E0255 は決して出ません。* で入ってきた名前より、自分のアイテムが黙って優先されます。便利ではありますが、どの Error を使っているのかがわかりにくくなることもあるので、明示的にインポートしたほうがたいていはわかりやすくなります。

  • E0252:2つの use の行が、fmt::Result と io::Result のように同じ名前を持ち込む。
  • E0428:自分のアイテム2つが同じ名前を使っている。関数をオーバーロードしようとしたときによく起こります。
  • E0592:impl ブロックの中に、同じ名前のメソッドが2つある。
  • E0432:use の行そのものが、存在しないものを指している。
  • E0425:逆の問題。使っている場所で、その名前がまったく定義されていない。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. ファイルの先頭に use std::fmt::Result; があり、あとで enum Result { Pass, Fail } を定義しました。どのエラーになりますか?

2. 1つのファイルで std::io::Error と自分の enum Error の両方が必要です。すっきりした直し方はどれですか?

3. rustc が E0255 と一緒に warning: unused import: std::io::Error も表示しました。この警告は何を教えてくれていますか?

スコア: 0 / 3