コンテンツにスキップ

Rustのエラー E0428: the name is defined multiple times(同じ名前の重複定義)

E0428 は、同じ場所に同じ名前のものを2つ定義した、という意味のエラーです。area という関数が2つ、User という構造体が2つ、あるいは列挙型と構造体がどちらも Status、といった場合です。Java や C#、C++ のように、関数をオーバーロード(多重定義)しようとしたときによく起こります。Rust にオーバーロードはありませんが、その代わりになる良い方法があります。このページでそれを紹介します。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0428]: the name `area` is defined multiple times
--> src/main.rs:5:1
|
1 | fn area(width: f64, height: f64) -> f64 {
| --------------------------------------- previous definition of the value `area` here
...
5 | fn area(radius: f64) -> f64 { // ❌ a second `area`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `area` redefined here
|
= note: `area` must be defined only once in the value namespace of this module

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

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

fn area(width: f64, height: f64) -> f64 {
width * height
}
fn area(radius: f64) -> f64 { // ❌ a second `area`
3.14159 * radius * radius
}
fn main() {
println!("{}", area(2.0, 3.0));
println!("{}", area(1.0));
}

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

このプログラムでは、rustc は2つ目のエラーも表示します。area(1.0) のところで出る E0061「this function takes 2 arguments but 1 argument was supplied」(この関数は引数を2つ取るのに、1つしか渡されていない)です。これはただの副作用です。Rust は1つ目の area を残して2つ目を無視したので、引数1つの呼び出しが合わなくなったのです。E0428 を直せば、E0061 も一緒に消えます。

rustc は2か所を指しています。

  1. --- の行(1行目):「previous definition of the value area here」(値 area の前の定義はここ)。これが1つ目の area で、Rust が残すほうです。
  2. ^^^ の行(5行目):「area redefined here」(area がここで再定義された)。これが2つ目で、受け付けてもらえないほうです。
  3. note: は、area は「in the value namespace of this module」(このモジュールの値の名前空間の中で)1回だけ定義しなければならない、と言っています。モジュールは1つの引き出しのようなもので、引き出しの中の名前はどれも重複してはいけません。

構造体が2つの場合は、注記に「type namespace」(型の名前空間)と出ます。Rust は、型(構造体、列挙型、トレイト、モジュール)と値(関数、定数、static)を、2つの別々のリストで管理しています。だから mod shapes と fn shapes は並んで存在できますが、fn area が2つや struct User が2つは許されないのです。

1つのフォルダに report.txt というファイルを2つ置こうとするようなものです。2つ目のファイルが悪いわけではありませんが、パソコンはそれを拒みます。report.txt と言っただけでは、どちらのファイルのことなのかわからなくなってしまうからです。

Rust では、モジュールの中の1つの名前は、ちょうど1つのアイテムを指します。area(...) と書くと、Rust は引数の数や型ではなく、名前だけで関数を見つけます。おかげでコードが読みやすくなります。調べる area は1つしかありませんし、新しい関数を追加しても、古い呼び出しがどの関数を使うかが黙って変わることはありません。

オーバーロードがある言語では、引数からどのバージョンを使うかを選びます。でも、数値どうしの変換やデフォルト値が絡んでくると、そのルールはややこしくなります。Rust は、そうしないことを選びました。「入力が違っても同じ名前を使いたい」ときは、トレイトやジェネリクスを使います。そうすれば、どれが選ばれるかが型から見えます。関数について詳しくは引数:関数にデータを渡すを見てください。

直し方1:それぞれに別の名前を付ける

Section titled “直し方1:それぞれに別の名前を付ける”

たいていはこれを選びます。違う入力を受け取る2つの関数は、たいてい違う仕事をしているので、名前でそれを表せます。片方が消し忘れた古いコピーなら、名前を変えるのではなく消しましょう。

fn rectangle_area(width: f64, height: f64) -> f64 {
width * height
}
fn circle_area(radius: f64) -> f64 {
3.14159 * radius * radius
}
fn main() {
println!("{}", rectangle_area(2.0, 3.0));
println!("{}", circle_area(1.0));
}

実行結果:

6
3.14159
▶ 実際に試してみよう

標準ライブラリも同じことをしています。String::new()、String::with_capacity(10)、String::from("hi") は、String を作る3つの方法に付けられた、3つの別々の名前です。

直し方2:別々のモジュールに入れる

Section titled “直し方2:別々のモジュールに入れる”

それぞれの場面で、短い名前が本当にいちばんいい名前のときは、これを選びます。図形ごとの area や、データベースのコードの User と API のコードの別の User などです。モジュールはそれぞれ自分の名前を持っているので、ぶつからなくなります。呼び出す側は、前にモジュール名を書きます。

mod rectangle {
pub fn area(width: f64, height: f64) -> f64 {
width * height
}
}
mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
fn main() {
println!("{}", rectangle::area(2.0, 3.0));
println!("{}", circle::area(1.0));
}

実行結果:

6
3.14159
▶ 実際に試してみよう

pub を忘れないようにしましょう。忘れると、main からの呼び出しが E0603「function is private」(関数が非公開)になります。詳しくはモジュールを定義するを見てください。

直し方3:オーバーロードの代わりにトレイトを使う

Section titled “直し方3:オーバーロードの代わりにトレイトを使う”

1つの呼び出しを、いろいろな型で使いたいときは、これを選びます。オーバーロードを使いたくなるのは、たいていこのためです。入力の種類ごとに型を作り、共通のトレイトを持たせます。こうすると、どの図形も自分の area を持ち、Rust は値の型から正しいものを選んでくれます。

trait Shape {
fn area(&self) -> f64;
}
struct Rectangle {
width: f64,
height: f64,
}
struct Circle {
radius: f64,
}
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
impl Shape for Circle {
fn area(&self) -> f64 {
3.14159 * self.radius * self.radius
}
}
fn print_area<T: Shape>(shape: &T) {
println!("面積: {}", shape.area());
}
fn main() {
print_area(&Rectangle { width: 2.0, height: 3.0 });
print_area(&Circle { radius: 1.0 });
}

実行結果:

面積: 6
面積: 3.14159
▶ 実際に試してみよう

直し方1よりコードは長くなるので、図形がいくつもあったり、同じパターンに従う関数(area、perimeter、draw)がいくつもあったりするときに役に立ちます。ジェネリックな print_area<T: Shape> は、あとで追加するどんな図形にも使えます。詳しくは関数の引数にトレイトを使うとジェネリクスを見てください。

「追加の引数があってもなくても使える同じ関数」が欲しいなら、もっと手軽な方法があります。fn greet(name: &str, title: Option<&str>) のように Option を受け取り、None か Some("Dr.") を渡して呼ぶのです。

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

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

自分のアイテムと use の行がぶつかっているときは、これを選びます。厳密に言うと、これは兄弟分の別のエラー E0255 で、見出しは同じです。

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 {
| ^^^^^^^^^^ `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;
| +++++++++++++

previous import of the type `Error` here は「型 Error の前のインポートはここ」、help は「as を使えばインポートの名前を変えられる」という意味です。

as で、インポートにこのファイルだけのあだ名を付けましょう。rustc が仮に付けた OtherError ではなく、IoError のように、どこから来たのかがわかる名前を選びます。

use std::io::Error as IoError;
use std::io::ErrorKind;
#[derive(Debug)]
enum Error {
NotFound(String),
Invalid(u32),
}
fn main() {
let mine = Error::NotFound(String::from("config.toml"));
let other = Error::Invalid(42);
let disk = IoError::new(ErrorKind::Other, "ディスクがいっぱいです");
println!("{:?}", mine);
println!("{:?}", other);
println!("{}", disk);
}

実行結果:

NotFound("config.toml")
Invalid(42)
ディスクがいっぱいです
▶ 実際に試してみよう

use std::fmt::Result; と use std::io::Result; のように、2つの use の行がぶつかると、3つ目の兄弟分 E0252(`Result` reimported here、Result がここで再びインポートされた)になります。直し方は同じで、use std::io::Result as IoResult; と書くか、モジュールをインポートして(use std::io;)io::Result と書きます。自分の列挙型の名前を(AppError などに)変えても直ります。

impl ブロックの中でのメソッドのオーバーロード。 impl Timer の中に fn new を2つ書くと、別のコード E0592 になります。duplicate definitions with name `new` (new という名前の定義が重複している)と表示されます。原因も直し方も同じで、Timer::new() と Timer::with_seconds(30) のように、やることに合った名前を付けます。詳しくは構造体を見てください。

リファクタリング中のコピー&ペースト。 関数を書き換えるためにコピーしたり、別のファイルから struct User を貼り付けたりして、古いほうを消し忘れる場合です。テストでもよくあります。1つの mod tests に #[test] fn it_works() が2つあると E0428 になります。直し方:古いコピーを消すか、テストの名前を変えます。

列挙型のバリアントの重複。 enum Color { Red, Red } も E0428 になります。2つ目の Red が指され、「type namespace of this enum」(この列挙型の型の名前空間)という注記が付きます。直し方:重複したバリアントを消すか、名前を変えます。詳しくは列挙型を見てください。

  • E0061:引数の数が違う。関数をオーバーロードしようとしたとき、E0428 のすぐあとによく表示されます。
  • E0425:逆の問題。使っている場所で、その名前が1回も定義されていない。
  • E0433:circle::area のようなパスの最初の部分(モジュール)が見つからない。
  • E0034:2つのトレイトが、型に同じ名前のメソッドを与えている。どちらのつもりなのかを書く必要があります。
  • E0255(use がローカルのアイテムとぶつかる)、E0252(2つの use の行がぶつかる)、E0592(impl の中のメソッドの重複):上で紹介した兄弟分のエラーです。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. 1つのファイルに fn total(a: u32) と fn total(a: u32, b: u32) を書きました。どうなりますか?

2. mod report と fn report() は、同じモジュールの中に共存できますか?

3. ファイルに use std::fmt::Result; があり、enum Result を定義しました。すっきりした直し方はどれですか?

スコア: 0 / 3