コンテンツにスキップ

Rustのエラー E0034: multiple applicable items in scope(同じ名前のメソッドが複数)

E0034 は、自分の型に同じ名前のメソッドが2つ(2つのトレイトから1つずつ)あるのに、book.describe() のような短い書き方で呼んだ、という意味のエラーです。Rust には両方が見えていて、勝手にどちらかを選んではくれません。型がおかしいわけではありません。どのトレイトのつもりなのかを書けばいいだけです。たいていは1行で済み、その1行も rustc が書いてくれます。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0034]: multiple applicable items in scope
--> src/main.rs:28:25
|
28 | println!("{}", book.describe()); // ❌ Summary's or Label's?
| ^^^^^^^^ multiple `describe` found
|
note: candidate #1 is defined in an impl of the trait `Label` for the type `Book`
--> src/main.rs:21:5
|
21 | fn describe(&self) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl of the trait `Summary` for the type `Book`
--> src/main.rs:15:5
|
15 | fn describe(&self) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: disambiguate the method for candidate #1
|
28 - println!("{}", book.describe()); // ❌ Summary's or Label's?
28 + println!("{}", Label::describe(&book)); // ❌ Summary's or Label's?
|
help: disambiguate the method for candidate #2
|
28 - println!("{}", book.describe()); // ❌ Summary's or Label's?
28 + println!("{}", Summary::describe(&book)); // ❌ Summary's or Label's?
|

「multiple applicable items in scope」は「スコープの中に、当てはまるアイテムが複数ある」という意味です。

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

trait Summary {
fn describe(&self) -> String;
}
trait Label {
fn describe(&self) -> String;
}
struct Book {
title: String,
pages: u32,
}
impl Summary for Book {
fn describe(&self) -> String {
format!("{} ({} pages)", self.title, self.pages)
}
}
impl Label for Book {
fn describe(&self) -> String {
format!("BOOK: {}", self.title.to_uppercase())
}
}
fn main() {
let book = Book { title: String::from("Rust for Crabs"), pages: 320 };
println!("{}", book.describe()); // ❌ Summary's or Label's?
}

コメントは「Summary のもの? それとも Label のもの?」という意味です(エラーになる例なので、コードはそのままにしています)。

メッセージを上から順に読んでいきましょう。

  1. describe の下の ^^^^^^^^:「multiple describe found」(describe が複数見つかった)。Rust は Book に describe というメソッドを探して、2つ以上見つけました。
  2. 2つの note: の行は候補の一覧です。1つは impl Label for Book(21行目)から、もう1つは impl Summary for Book(15行目)から来ています。どちらも本物で、どちらも動き、返す文字列は違います。
  3. 2つの help: の行は、それぞれを選んだときの直し方です(disambiguate は「あいまいさをなくす」という意味です)。Summary::describe(&book) は「Summary から来た describe を、&book を self にして呼ぶ」という意味です。

同じオフィスに「佐藤さん」が2人いるようなものです。「佐藤さん!」と呼んでも、どちらが振り向けばいいかわかりません。「経理の佐藤さん」と言えば伝わります。Summary::describe が「経理の佐藤さん」です。

book.describe() のようなメソッド呼び出しは、近道の書き方です。Rust は book の型を見て、その型自身のメソッドと、スコープの中にあって Book に実装されているすべてのトレイトのメソッドを探します。ふつうは、ちょうど1つが見つかります。ここでは2つ見つかり、しかもやることが違います。1つは要約を、もう1つは本棚用のラベルを返します。

「先に定義されたトレイトを優先する」のような隠れたルールで、Rust がどちらかを選ぶこともできたでしょう。でもそうすると、トレイトを1つ追加したり、use の行の順番を入れ替えたりしただけで、プログラムの表示が黙って変わってしまいます。Rust は当てずっぽうを拒み、誰が読んでもわかるように、コードの中で一度はっきり書いてほしいと頼んでいるのです。

例外が1つあります。型自身の impl Book ブロックにあるメソッドは、同じ名前のトレイトのメソッドよりいつも優先されるので、その場合は E0034 になりません。詳しくはトレイトを実装すると完全修飾構文:名前がぶつかったときを見てください。

直し方1:トレイトを通して呼ぶ:Trait::method(&x)

Section titled “直し方1:トレイトを通して呼ぶ:Trait::method(&x)”

両方のメソッドが必要で、そのたびにどちらかを書けばいいときは、これを選びます。rustc が提案しているのも、まさにこの方法です。値を最初の引数として渡します。そのとき、メソッドの &self に合わせて同じ & を付けます。

trait Summary {
fn describe(&self) -> String;
}
trait Label {
fn describe(&self) -> String;
}
struct Book {
title: String,
pages: u32,
}
impl Summary for Book {
fn describe(&self) -> String {
format!("{} ({} pages)", self.title, self.pages)
}
}
impl Label for Book {
fn describe(&self) -> String {
format!("BOOK: {}", self.title.to_uppercase())
}
}
fn main() {
let book = Book { title: String::from("Rust for Crabs"), pages: 320 };
println!("{}", Summary::describe(&book));
println!("{}", Label::describe(&book));
}

実行結果:

Rust for Crabs (320 pages)
BOOK: RUST FOR CRABS
▶ 実際に試してみよう

上のエラーになる例と見比べやすいように、表示する文字列は英語のままにしています。

メソッドが &mut self を受け取るなら &mut book を、self を受け取るなら book を渡します(ドットで呼んだときと同じように、値はムーブされます)。

直し方2:完全修飾構文:<Type as Trait>::method()

Section titled “直し方2:完全修飾構文:<Type as Trait>::method()”

ぶつかっている関数に self がないとき(コンストラクタや fn category() など)は、これを選びます。この場合、Rust が手がかりにできる値がないので、Trait::category() だけでは足りません。型の名前も書く必要があります。<Book as Summary>::category() は「Book が Summary を実装しているから持っている category」と読みます。これはメソッドにも使えます。

trait Summary {
fn category() -> String;
fn describe(&self) -> String;
}
trait Label {
fn category() -> String;
}
struct Book {
title: String,
}
impl Summary for Book {
fn category() -> String { String::from("ノンフィクション") }
fn describe(&self) -> String { format!("『{}』という本", self.title) }
}
impl Label for Book {
fn category() -> String { String::from("B棚") }
}
fn main() {
let book = Book { title: String::from("Rust for Crabs") };
println!("{}", <Book as Summary>::category());
println!("{}", <Book as Label>::category());
println!("{}", <Book as Summary>::describe(&book));
}

実行結果:

ノンフィクション
B棚
『Rust for Crabs』という本
▶ 実際に試してみよう

ここで Book::category() と書いても E0034 になり、use fully-qualified syntax to disambiguate(あいまいさをなくすには完全修飾構文を使ってください)というヒントが出ます。Summary::category() とだけ書くと、別のエラー E0790 になります。トレイトには、どの型のことなのかがまだわからないからです。

直し方3:どちらかのメソッドの名前を変える

Section titled “直し方3:どちらかのメソッドの名前を変える”

両方のトレイトが自分のもので、名前がたまたま同じになっただけなら、これを選びます。違うことをする2つのメソッドには、違う名前が似合います。そうすれば、短い book.describe() がまた使えるようになります。多くの場合、これがいちばんすっきりした直し方です。

trait Summary {
fn describe(&self) -> String;
}
trait Label {
fn label_text(&self) -> String;
}
struct Book {
title: String,
pages: u32,
}
impl Summary for Book {
fn describe(&self) -> String { format!("{} ({} pages)", self.title, self.pages) }
}
impl Label for Book {
fn label_text(&self) -> String { format!("BOOK: {}", self.title.to_uppercase()) }
}
fn main() {
let book = Book { title: String::from("Rust for Crabs"), pages: 320 };
println!("{}", book.describe());
println!("{}", book.label_text());
}

実行結果:

Rust for Crabs (320 pages)
BOOK: RUST FOR CRABS
▶ 実際に試してみよう

もちろん、ほかのクレートのトレイトのメソッドは名前を変えられません。その場合は、直し方1か直し方4を使いましょう。

直し方4:必要なトレイトだけをインポートする

Section titled “直し方4:必要なトレイトだけをインポートする”

トレイトが別々のモジュールやクレートにあるときは、これを選びます。トレイトのメソッドをドットで呼べるのは、そのトレイトがスコープの中にあるときだけです。だから、コードのある部分で片方しか必要ないなら、そこではその1つだけをインポートしましょう。use は関数の中にも書けて、そうするとその関数の中だけで有効になります。

mod report {
pub trait Summary {
fn describe(&self) -> String;
}
}
mod shop {
pub trait Label {
fn describe(&self) -> String;
}
}
struct Book {
title: String,
}
impl report::Summary for Book {
fn describe(&self) -> String { format!("『{}』という本", self.title) }
}
impl shop::Label for Book {
fn describe(&self) -> String { format!("BOOK: {}", self.title.to_uppercase()) }
}
fn print_label(book: &Book) {
use shop::Label; // ここでスコープにあるのは Label だけ
println!("{}", book.describe());
}
fn main() {
use report::Summary; // ここでスコープにあるのは Summary だけ
let book = Book { title: String::from("Rust for Crabs") };
println!("{}", book.describe());
print_label(&book);
}

実行結果:

『Rust for Crabs』という本
BOOK: RUST FOR CRABS
▶ 実際に試してみよう

impl ブロックでは report::Summary と shop::Label を省略せずに書いていることに注目してください。トレイトを実装するのに use はいりません。use が必要なのは、ドットで呼ぶときだけです。use について詳しくはモジュールを見てください。

自分で作った to_string()。 Display を実装している型は、標準ライブラリの ToString トレイトから、もう to_string() をもらっています。そこに to_string メソッドを持つ自分のトレイトを足すと、point.to_string() が E0034 になり、candidate #2 is defined in an impl of the trait `ToString` for the type `T` (候補2はトレイト ToString の型 T への impl で定義されている)と表示されます。直し方:自分のほうの名前を変える(pretty()、to_label() など)か、Pretty::to_string(&point) と呼びます。

2つの prelude::* のインポート。 ライブラリはよく、自分のトレイトをまとめて取り込めるように use some_crate::prelude::*; を用意しています。同じ型に draw() や render() メソッドを追加するプレリュードを2つインポートすると、ドットでの呼び出しがあいまいになります。直し方:片方の * を、実際に使うアイテムだけのインポートに置き換えるか、Render::draw(&data) と呼びます。

トレイト境界が2つあるジェネリックな関数。 fn show<T: Summary + Label>(item: &T) の中では、item.describe() が E0034 になります。ここでは rustc の提案に気をつけてください。Label::describe(&item) を提案してきますが、item はすでに参照なので、そう書くと E0277(the trait bound `&T: Label` is not satisfied、&T が Label を実装していない)になります。余分な & を付けずに Label::describe(item) と書きましょう。詳しくはジェネリクスを見てください。

  • E0790:Default::default() や Summary::category() のように、self のないトレイトの関数を、型を指定せずに呼んだ。
  • E0599:逆の問題。その名前のメソッドが1つも見つからない。トレイトをインポートしていないことがよくあります。
  • E0277:トレイト境界を満たしていない。たとえば Trait::method(&x) に余分な & を付けたとき。
  • E0428:同じ名前が複数回定義されている。2つのトレイトのメソッドではなく、1つのモジュールの中に同じ名前のアイテムが2つある場合です。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. Book は Summary と Label を実装していて、どちらにも fn describe(&self) があります。Label のほうを呼ぶのはどの行ですか?

2. どちらのトレイトにも、self のない fn category() -> String があります。動くのはどの呼び出しですか?

3. Book には自分の impl Book { fn describe(&self) ... } があり、describe を持つ Summary も実装しています。book.describe() はどうなりますか?

スコア: 0 / 3