コンテンツにスキップ

Rustのエラー E0592: duplicate definitions with name x(メソッドの重複定義)

E0592 は、1つの型に、同じ名前のメソッド、関連関数、または定数が impl ブロックの中で2つ書かれている、という意味のエラーです。1つの型に impl ブロックはいくつ書いてもかまいませんが、それらはすべて1つの名前のリストを共有しています。だから、それぞれの名前は1回しか使えません。たいていはコピー&ペースト、マージ、または別のファイルにある2つ目の impl ブロックが原因です。直し方は、1つだけ残すか、もう片方に新しい名前を付けることです。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0592]: duplicate definitions with name `reset`
--> src/main.rs:10:5
|
10 | fn reset(&mut self) {
| ^^^^^^^^^^^^^^^^^^^ duplicate definitions for `reset`
...
16 | fn reset(&mut self) { // ❌ `reset` is already defined above
| ------------------- other definition for `reset`

「duplicate definitions with name reset」は「reset という名前の定義が重複している」という意味です。

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

struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
fn reset(&mut self) {
self.count = 0;
}
}
impl Counter {
fn reset(&mut self) { // ❌ `reset` is already defined above
self.count = 0;
}
}
fn main() {
let mut c = Counter::new();
c.reset();
println!("{}", c.count);
}

コメントは「reset は上ですでに定義されている」という意味です(エラーになる例なので、コードはそのままにしています)。

rustc は、c.reset() の呼び出しのところで E0034「multiple applicable items in scope」(スコープ内に使えるアイテムが複数ある)も報告します。2つの reset のどちらのことなのか、わからないからです。これはあとから付いてくるエラーなので、E0592 を直せば E0034 も消えます。

見出しの行には、2回定義されている名前(reset)が出ています。^^^ が一方の定義を示し、--- の行が「other definition」(もう1つの定義)を示しています。rustc は両方の行番号を表示してくれるので、すぐにそこへ飛べます。2つが離れているときに間に出る ... は、「何行か省略しています」という意味にすぎません。

2つの reset が別々の impl Counter ブロックにあっても、クレートの別々のファイルにあっても、関係ありません。Rust にとっては、どれも同じ型のメソッドです。だから Counter::reset は、ちょうど1つの関数を指さなければなりません。関連定数にも同じルールがあります。1つの型に const MAX: u32 が2つあれば、それも E0592 です。

スマホの連絡先に「お母さん」という登録が2つあるようなものです。「お母さんに電話して」と言われたら、どちらの番号にかければいいのでしょうか。

メソッドをいくつかの impl ブロックに分けるのは許されていて、便利なこともよくあります。コンストラクタ用のブロック、ゲッター用のブロック、#[cfg(test)] の中のブロック、といった具合です。詳しくはメソッドで振る舞いを追加するを見てください。でも、c.reset() や Counter::reset(&mut c) と呼び出したとき、Rust は関数を1つ見つけなければなりません。もし2つあったら、どちらが動くかは、呼び出しを読んでもわからない細かい事情で決まってしまいます。

だから Rust は、呼び出しのところだけでなく、定義のところで断ります。これで本物のバグを防げます。コピーしたメソッドだと、片方のコピーで問題を直しても、もう片方のコピーが動き続けているかもしれません。Rust は、直すべきバージョンがいつも1つだけになるようにしてくれるのです。

直し方1:重複を消して、定義を1つだけ残す

Section titled “直し方1:重複を消して、定義を1つだけ残す”

たいていはこれを選びます。2つのコピーが同じことをしているなら(コピー&ペースト、うまくいかなかったマージ、移動したのに元を消し忘れたメソッドなど)、片方を消します。消す前に、2つの中身を見比べましょう。片方にだけ修正が入っているなら、そちらを残します。impl ブロックを1つにまとめておくと、この先、重複にも気づきやすくなります。

struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
fn increment(&mut self) {
self.count += 1;
}
fn reset(&mut self) { // ✅ `Counter` の `reset` は1つだけ
self.count = 0;
}
}
fn main() {
let mut c = Counter::new();
c.increment();
c.increment();
println!("リセット前: {}", c.count);
c.reset();
println!("リセット後: {}", c.count);
}

実行結果:

リセット前: 2
リセット後: 0
▶ 実際に試してみよう

2つ目の定義が見つからないときは、プロジェクト全体を検索しましょう。grep -rn "fn reset" src/ です。impl Counter ブロックは、クレートのどのモジュールにでも置けるからです。

2つの定義が違うことをしているなら、これを選びます。Rust にはオーバーロードがありません。reset() と reset(start: u32) のように、名前が同じで引数が違うメソッドを2つ持つことはできず、これも E0592 になります。それぞれに、何をするのかがわかる名前を付けましょう。

struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
fn reset(&mut self) {
self.count = 0;
}
}
impl Counter {
fn reset_to(&mut self, start: u32) { // ✅ 違う動きには違う名前を
self.count = start;
}
}
fn main() {
let mut c = Counter::new();
c.reset_to(10);
println!("reset_to(10) のあと: {}", c.count);
c.reset();
println!("reset() のあと: {}", c.count);
}

実行結果:

reset_to(10) のあと: 10
reset() のあと: 0
▶ 実際に試してみよう

Rust でよく使われる名前の付け方が参考になります。new と with_capacity、from_str と from_bytes、get と get_mut などです。いろいろな引数の型に、どうしても1つの名前を使いたいなら、fn reset_to(&mut self, start: impl Into<u32>) のように、ジェネリックな引数かトレイトを使います。詳しくはジェネリクスを見てください。

直し方3:ジェネリックな impl ブロックが重ならないようにする

Section titled “直し方3:ジェネリックな impl ブロックが重ならないようにする”

2つの定義の片方が、impl<T> Wrapper<T> のようなジェネリックなブロックにあるときは、これを選びます。このブロックは Wrapper<i32> もカバーしているので、同じ名前のメソッドを持つ2つ目の impl Wrapper<i32> は重複になります。

error[E0592]: duplicate definitions with name `describe`
--> src/main.rs:4:5
|
4 | fn describe(&self) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definitions for `describe`
...
10 | fn describe(&self) -> String {
| ---------------------------- other definition for `describe`

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

struct Wrapper<T>(T);
impl<T> Wrapper<T> {
fn describe(&self) -> String {
String::from("something")
}
}
impl Wrapper<i32> {
fn describe(&self) -> String {
format!("the number {}", self.0)
}
}
fn main() {
println!("{}", Wrapper(5).describe());
}

Rust では、特定の型向けの impl でジェネリックな impl を上書きすることはできません(この機能は特殊化(specialization)と呼ばれ、まだ安定版にはありません)。良い抜け道は2つあります。特定の型向けの impl ブロックだけを書いて重ならないようにするか、振る舞いをトレイトに入れて、型ごとに1回ずつ実装するかです。

struct Wrapper<T>(T);
// 方法A:特定の型向けの impl ブロックは重ならない
impl Wrapper<i32> {
fn describe(&self) -> String {
format!("数 {}", self.0)
}
}
impl Wrapper<&str> {
fn describe(&self) -> String {
format!("テキスト「{}」", self.0)
}
}
// 方法B:トレイトを、型ごとに1回ずつ実装する
trait Label {
fn label(&self) -> String;
}
impl Label for i32 {
fn label(&self) -> String {
format!("整数 {}", self)
}
}
impl Label for bool {
fn label(&self) -> String {
format!("フラグ {}", self)
}
}
impl<T: Label> Wrapper<T> {
fn label(&self) -> String {
self.0.label() // ジェネリックなメソッドは1つ。型ごとの部分はトレイトにある
}
}
fn main() {
println!("{}", Wrapper(5).describe());
println!("{}", Wrapper("hi").describe());
println!("{}", Wrapper(7).label());
println!("{}", Wrapper(true).label());
}

実行結果:

数 5
テキスト「hi」
整数 7
フラグ true
▶ 実際に試してみよう

長い目で見ると、たいていはトレイト版のほうが良い設計です。新しい型に対応したくなったら、impl Label for ... を1つ足すだけで済みます。詳しくはトレイトを見てください。

2つ目の impl ブロックに書いたテスト用のヘルパー。 テスト用の値を作るために、#[cfg(test)] mod tests の中に impl Counter { fn new() -> Self { ... } } を書いたけれど、Counter::new はすでにあった、という場合です。テスト用のブロックも、やはり impl Counter です。直し方:fn for_test() のような名前にするか、本物の new を使います。詳しくはテストを見てください。

マージやリファクタリングのあと。 2つのブランチがどちらも fn len(&self) を追加した、あるいはメソッドを新しいファイルに移したのに古いコピーが残っていた、という場合です。rustc が両方の行番号を見せてくれるので、良いほうを残しましょう。

同じ名前のトレイトのメソッドなら問題ない。 impl Counter { fn reset(&mut self) } と impl Resettable for Counter { fn reset(&mut self) } を一緒に書いても、E0592 にはなりません。片方は型そのもののメソッドで、もう片方はトレイトのものだからです。c.reset() は型そのもののほうを呼びます。同じ名前のメソッドを持つトレイトが2つある場合もコンパイルできますが、c.reset() と呼ぶと E0034 になります。Resettable::reset(&mut c) と書けば直ります。

理解度チェック

1. ある型に impl ブロックが2つあります。どちらにも reset というメソッドを書けますか?

2. area() と area(scale: f64) の両方が欲しいとき、Rust ではどうしますか?

3. impl<T> Wrapper<T> と impl Wrapper<i32> の両方が describe を定義すると、なぜぶつかるのですか?

スコア: 0 / 3