跳转到内容

Rust 错误 E0034:multiple applicable items in scope(有多个同名方法)

E0034 的意思是:你的类型有两个同名的方法,分别来自两个 trait,而你用简短的方式调用了它,比如 book.describe()。Rust 两个都看得到,不会替你挑一个。你的类型并没有出什么问题,只需要说明你指的是哪个 trait。这通常只要改一行,rustc 甚至会帮你写好。本页是编译错误指南的一部分。

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?
|

第一行的意思是“作用域内有多个可用的项”。

产生这个错误的程序是这样的:

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?
}

从上往下读这条信息:

  1. describe 下面的 ^^^^^^^^:“multiple describe found”(找到了多个 describe)。Rust 在 Book 上找名为 describe 的方法,找到了不止一个。
  2. 两行 note: 列出了候选项:一个来自 impl Label for Book(第 21 行),另一个来自 impl Summary for Book(第 15 行)。两个都是真的,都能用,返回的文字却不一样。
  3. 两行 help: 给出了每种选择的修法。Summary::describe(&book) 的意思是“调用来自 Summary 的那个 describe,并把 &book 作为 self 传进去”。

想象一个办公室里有两位同事都叫小王。喊一声“小王!”是不行的,你得说“财务部的小王”。Summary::describe 就是“财务部的小王”。

像 book.describe() 这样的方法调用是一种简写。Rust 会先看 book 的类型,然后在这个类型自己的方法,以及所有在作用域内、并且为 Book 实现了的 trait 的方法里查找。通常只会找到一个。这里找到了两个,而且它们做的事不一样:一个打印摘要,另一个打印书架标签。

Rust 本可以按某种隐藏的规则挑一个,比如“先定义的 trait 优先”。但那样的话,加一个 trait,或者调换两行 use 的顺序,都会悄悄改变程序打印的内容。Rust 拒绝去猜,而是让你在代码里明确写一次,让每个读代码的人都看得见。

有一个例外:类型自己的 impl Book 块里的方法,总是优先于同名的 trait 方法,所以这种情况永远不会报 E0034。更多内容见 trait 和高级 trait。

修复 1:通过 trait 调用:Trait::method(&x)

Section titled “修复 1:通过 trait 调用: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!("{}({} 页)", self.title, self.pages)
}
}
impl Label for Book {
fn describe(&self) -> String {
format!("图书:{}", 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 页)
图书: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,并附带 help use fully-qualified syntax to disambiguate(用完全限定语法消除歧义)。只写 Summary::category() 则会得到另一个错误 E0790,因为 trait 仍然不知道你指的是哪个类型。

当两个 trait 都是你自己写的,而同名只是巧合时,选这个。做不同事情的两个方法理应有不同的名字,改完之后,简短的 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!("{}({} 页)", self.title, self.pages) }
}
impl Label for Book {
fn label_text(&self) -> String { format!("图书:{}", 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 页)
图书:RUST FOR CRABS
▶ 动手试一试

当然,来自其他 crate 的 trait,你没法给它的方法改名。这种情况请用修复 1 或修复 4。

修复 4:只导入你需要的那个 trait

Section titled “修复 4:只导入你需要的那个 trait”

当这些 trait 位于不同的模块或 crate 时,选这个。只有当 trait 在作用域内时,它的方法才能用点号语法调用。所以如果代码的某一部分只需要其中一个,就只在那里导入那一个。你甚至可以把 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!("图书:{}", 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》的书
图书:RUST FOR CRABS
▶ 动手试一试

注意,impl 块里完整地写出了 report::Summary 和 shop::Label。实现一个 trait 不需要 use,只有用点号调用时才需要。关于 use 的更多内容,见模块。

你自己的 to_string()。 每个实现了 Display 的类型,都已经从标准库的 ToString trait 得到了 to_string()。如果你再加一个带 to_string 方法的自定义 trait,point.to_string() 就会报 E0034,并显示 candidate #2 is defined in an impl of the trait `ToString` for the type `T` 。修复:给你自己的方法改名(pretty()、to_label()),或者调用 Pretty::to_string(&point)。

两个 prelude::* 导入。 库经常提供 use some_crate::prelude::*;,用来引入它们的 trait。如果你导入了两个 prelude,而它们都给同一个类型加了 draw() 或 render() 方法,点号调用就会产生歧义。修复:把其中一个通配导入换成你实际用到的那几项,或者调用 Render::draw(&data)。

带两个约束的泛型函数。 在 fn show<T: Summary + Label>(item: &T) 里,item.describe() 会报 E0034。这里要小心 rustc 的建议:它给出的是 Label::describe(&item),但 item 本来就是引用,这样写会得到 E0277(the trait bound `&T: Label` is not satisfied)。去掉多余的 &,写成 Label::describe(item)。参见泛型。

  • E0790:调用了没有 self 的 trait 函数,比如 Default::default() 或 Summary::category(),却没有说明是哪个类型。
  • E0599:相反的问题,找不到这个名字的方法。常常是因为没有导入 trait。
  • E0277:不满足 trait 约束,比如在 Trait::method(&x) 里多写了一个 &。
  • E0428:同一个名字被定义了多次。指的是一个模块里有两个同名的项,而不是两个 trait 方法。
  • 所有编译错误:错误页面的完整列表。

小测验

1. Book 实现了 Summary 和 Label,两者都有 fn describe(&self)。哪一行调用的是 Label 的版本?

2. 两个 trait 都有不带 self 的 fn category() -> String。哪种调用能用?

3. Book 有自己的 impl Book { fn describe(&self) ... },同时还实现了也有 describe 的 Summary。book.describe() 会怎样?

得分: 0 / 3