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?}这个错误是什么意思
Section titled “这个错误是什么意思”从上往下读这条信息:
describe下面的^^^^^^^^:“multipledescribefound”(找到了多个describe)。Rust 在Book上找名为describe的方法,找到了不止一个。- 两行
note:列出了候选项:一个来自impl Label for Book(第 21 行),另一个来自impl Summary for Book(第 15 行)。两个都是真的,都能用,返回的文字却不一样。 - 两行
help:给出了每种选择的修法。Summary::describe(&book)的意思是“调用来自Summary的那个describe,并把&book作为self传进去”。
想象一个办公室里有两位同事都叫小王。喊一声“小王!”是不行的,你得说“财务部的小王”。Summary::describe 就是“财务部的小王”。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”像 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 仍然不知道你指的是哪个类型。
修复 3:给其中一个方法改名
Section titled “修复 3:给其中一个方法改名”当两个 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
- E0034 — Rust error codes index(英文):官方解释,附有两种完全限定的写法。
- 高级 trait — Rust 程序设计语言:讲解完全限定语法的小节,附有
Pilot、Wizard和Human的示例。 - 消除重叠 trait — 通过例子学 Rust:一个简短的可运行示例,两个 trait 有同名的方法。
- Trait:定义共同行为 — Rust 程序设计语言:定义和实现 trait,以及为什么必须让 trait 在作用域内才能用它的方法。
- Method call expressions — The Rust Reference(英文):Rust 为
x.method()查找背后方法时遵循的确切规则。