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のコンパイルエラー解説を見てください。
エラーメッセージ
Section titled “エラーメッセージ”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` here2 |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 modulehelp: 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行目のインポートと同じ名前」という意味です(エラーになる例なので、コードはそのままにしています)。
エラーの意味
Section titled “エラーの意味”rustc は3つのことを示しています。
---の行(1行目):「previous import of the typeErrorhere」(型Errorの前のインポートはここ)。useの行が、このモジュールにすでにErrorという名前を入れています。^^^の行(3行目):「Errorredefined here」(Errorがここで再定義された)。自分のenum Errorが、同じ名前を使おうとしています。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 がこうする理由
Section titled “Rust がこうする理由”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), }}実行結果:
名前が空ですよく出会う場面
Section titled “よく出会う場面”標準ライブラリと同じ名前の自分の関数。 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 を使っているのかがわかりにくくなることもあるので、明示的にインポートしたほうがたいていはわかりやすくなります。
関連するエラー
Section titled “関連するエラー”- E0252:2つの
useの行が、fmt::Resultとio::Resultのように同じ名前を持ち込む。 - E0428:自分のアイテム2つが同じ名前を使っている。関数をオーバーロードしようとしたときによく起こります。
- E0592:
implブロックの中に、同じ名前のメソッドが2つある。 - E0432:
useの行そのものが、存在しないものを指している。 - E0425:逆の問題。使っている場所で、その名前がまったく定義されていない。
- コンパイルエラーの一覧:エラー解説ページの全リストです。
理解度チェック
Section titled “理解度チェック”理解度チェック
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
さらに詳しく
Section titled “さらに詳しく”- E0255 — Rust error codes index(英語):このエラーの公式の説明です。
use bar::foo;がfn fooとぶつかる例を、asや親のパスを使って直しています。 - 新しい名前をasキーワードで与える — The Rust Programming Language 日本語版:
asでインポートの名前を変える方法を、2つのResult型の例で説明しています。 - Use declarations — The Rust Reference(英語):
as、グループ、グロブを含む、useのすべての書き方です。 - Namespaces — The Rust Reference(英語):エラーの注記に出てくる、型の名前空間と値の名前空間です。
- use宣言 — Rust By Example 日本語版:インポートと名前の変更の、短くて実行できる例です。