Bỏ qua để đến nội dung

Lỗi Rust E0034: multiple applicable items in scope (phương thức trùng tên)

E0034 nghĩa là kiểu của bạn có hai phương thức trùng tên, mỗi cái đến từ một trong hai trait, và bạn gọi theo cách ngắn gọn, như book.describe(). Rust thấy cả hai và sẽ không tự chọn giúp bạn. Các kiểu của bạn không hề bị hỏng; bạn chỉ cần nói rõ mình muốn trait nào. Việc đó thường chỉ mất một dòng, và rustc thậm chí viết sẵn cho bạn. Trang này thuộc loạt bài Giải thích các lỗi trình biên dịch 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?
|

Trình biên dịch báo có nhiều mục áp dụng được trong phạm vi: hai phương thức describe, và gợi ý cách gọi rõ ràng cho từng cái. Đây là chương trình gây ra lỗi:

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

Hãy đọc thông báo từ trên xuống dưới:

  1. ^^^^^^^^ dưới describe: “multiple describe found” (tìm thấy nhiều describe). Rust tìm một phương thức tên describe trên Book và thấy nhiều hơn một.
  2. Hai dòng note: liệt kê các ứng viên: một đến từ impl Label for Book (dòng 21), cái kia từ impl Summary for Book (dòng 15). Cả hai đều có thật, đều chạy được, và trả về văn bản khác nhau.
  3. Hai dòng help: cho thấy cách sửa cho từng lựa chọn. Summary::describe(&book) nghĩa là “gọi describe đến từ Summary, với &book làm self”.

Hãy nghĩ đến hai đồng nghiệp cùng tên Sơn trong một văn phòng. Gọi to “Sơn ơi!” thì không được; bạn phải nói “Sơn bên kế toán”. Summary::describe chính là “Sơn bên kế toán”.

Một lời gọi phương thức như book.describe() là một lối tắt. Rust nhìn vào kiểu của book, rồi tìm trong các phương thức riêng của nó và các phương thức của mọi trait đang nằm trong phạm vi (in scope) và được cài đặt cho Book. Thường thì lần tìm đó thấy đúng một kết quả. Ở đây nó thấy hai, và chúng làm những việc khác nhau: một cái in ra phần tóm tắt, cái kia in ra nhãn dán trên kệ.

Rust có thể chọn một cái theo quy tắc ngầm nào đó, như “trait được định nghĩa trước”. Nhưng khi đó, thêm một trait, hoặc đổi thứ tự hai dòng use, sẽ âm thầm thay đổi thứ chương trình của bạn in ra. Rust từ chối đoán và yêu cầu bạn nói rõ một lần, ngay trong code, nơi mọi người đọc đều thấy.

Có một ngoại lệ: một phương thức trong khối impl Book riêng của kiểu luôn thắng phương thức trait cùng tên, nên trường hợp đó không bao giờ cho ra E0034. Bạn có thể đọc thêm ở Trait và Trait nâng cao.

Chọn cách này khi bạn cần cả hai phương thức và chỉ muốn nói rõ mỗi lần dùng cái nào. Đây chính xác là điều rustc gợi ý. Giá trị được đưa vào làm đối số đầu tiên, với cùng dấu & mà &self của phương thức mong đợi:

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!("{} ({} trang)", self.title, self.pages)
}
}
impl Label for Book {
fn describe(&self) -> String {
format!("SÁCH: {}", 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));
}

Kết quả:

Rust for Crabs (320 trang)
SÁCH: RUST FOR CRABS
▶ Tự chạy thử

Tên sách được giữ tiếng Anh vì nó được đổi sang chữ hoa bằng to_uppercase(). Nếu phương thức nhận &mut self, hãy truyền &mut book. Nếu nó nhận self, hãy truyền book (và nó bị move, y như khi dùng dấu chấm).

Cách sửa 2: Cú pháp đầy đủ: <Type as Trait>::method()

Phần tiêu đề “Cách sửa 2: Cú pháp đầy đủ: <Type as Trait>::method()”

Chọn cách này khi hàm bị trùng không có self, như một hàm khởi tạo hay một fn category(). Khi đó không có giá trị nào để Rust nhìn vào, nên Trait::category() là chưa đủ: bạn còn phải nêu tên kiểu. <Book as Summary>::category() được đọc là “hàm category mà Book có nhờ cài đặt Summary”. Nó cũng dùng được cho phương thức:

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("Sách phi hư cấu") }
fn describe(&self) -> String { format!("Một cuốn sách tên {}", self.title) }
}
impl Label for Book {
fn category() -> String { String::from("Kệ 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));
}

Kết quả:

Sách phi hư cấu
Kệ B
Một cuốn sách tên Rust for Crabs
▶ Tự chạy thử

Viết Book::category() ở đây cũng cho ra E0034, kèm gợi ý use fully-qualified syntax to disambiguate. Còn chỉ viết Summary::category() thì cho ra một lỗi khác, E0790, vì trait vẫn không biết bạn muốn kiểu nào.

Cách sửa 3: Đổi tên một trong hai phương thức

Phần tiêu đề “Cách sửa 3: Đổi tên một trong hai phương thức”

Chọn cách này khi cả hai trait đều là của bạn và việc trùng tên chỉ là tình cờ. Hai phương thức làm hai việc khác nhau xứng đáng có hai cái tên khác nhau, và khi đó cách viết ngắn book.describe() lại chạy được. Đây thường là cách sửa gọn gàng nhất:

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!("{} ({} trang)", self.title, self.pages) }
}
impl Label for Book {
fn label_text(&self) -> String { format!("SÁCH: {}", self.title.to_uppercase()) }
}
fn main() {
let book = Book { title: String::from("Rust for Crabs"), pages: 320 };
println!("{}", book.describe());
println!("{}", book.label_text());
}

Kết quả:

Rust for Crabs (320 trang)
SÁCH: RUST FOR CRABS
▶ Tự chạy thử

Tất nhiên bạn không thể đổi tên phương thức của trait đến từ crate khác. Với những trait đó, hãy dùng Cách sửa 1 hoặc Cách sửa 4.

Chọn cách này khi các trait nằm ở các module hoặc crate khác nhau. Phương thức của một trait chỉ dùng được với cú pháp dấu chấm khi trait đó nằm trong phạm vi. Vì vậy nếu một phần code chỉ cần một trong hai, hãy chỉ import trait đó ở đó. Bạn thậm chí có thể đặt use bên trong một hàm, để nó chỉ có tác dụng trong hàm đó:

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!("Một cuốn sách tên {}", self.title) }
}
impl shop::Label for Book {
fn describe(&self) -> String { format!("SÁCH: {}", self.title.to_uppercase()) }
}
fn print_label(book: &Book) {
use shop::Label; // ở đây chỉ có Label trong phạm vi
println!("{}", book.describe());
}
fn main() {
use report::Summary; // ở đây chỉ có Summary trong phạm vi
let book = Book { title: String::from("Rust for Crabs") };
println!("{}", book.describe());
print_label(&book);
}

Kết quả:

Một cuốn sách tên Rust for Crabs
SÁCH: RUST FOR CRABS
▶ Tự chạy thử

Hãy để ý các khối impl viết đầy đủ report::Summary và shop::Label. Cài đặt một trait không cần use; chỉ lời gọi bằng dấu chấm mới cần. Xem thêm về use ở Module.

to_string() của riêng bạn. Mọi kiểu cài đặt Display đều đã có sẵn to_string() từ trait chuẩn ToString. Thêm một trait của riêng bạn có phương thức to_string, và point.to_string() sẽ cho ra E0034, kèm candidate #2 is defined in an impl of the trait `ToString` for the type `T` . Cách sửa: đổi tên phương thức của bạn (pretty(), to_label()), hoặc gọi Pretty::to_string(&point).

Hai lần import prelude::*. Các thư viện thường cung cấp use some_crate::prelude::*; để đưa các trait của chúng vào. Import hai prelude cùng thêm một phương thức draw() hay render() cho cùng một kiểu, và lời gọi bằng dấu chấm trở nên mơ hồ. Cách sửa: thay một glob import bằng đúng những mục bạn dùng, hoặc gọi Render::draw(&data).

Hàm generic có hai ràng buộc. Trong fn show<T: Summary + Label>(item: &T), item.describe() cho ra E0034. Cẩn thận với gợi ý của rustc ở đây: nó đưa ra Label::describe(&item), nhưng item đã là một tham chiếu rồi, nên như vậy sẽ cho ra E0277 (the trait bound `&T: Label` is not satisfied). Hãy viết Label::describe(item) không có & thừa. Xem Generic.

  • E0790: bạn gọi một hàm của trait không có self, như Default::default() hay Summary::category(), mà không nói rõ kiểu nào.
  • E0599: vấn đề ngược lại, không tìm thấy phương thức nào tên đó. Thường là do trait chưa được import.
  • E0277: ràng buộc trait không được thỏa mãn, ví dụ sau một dấu & thừa trong Trait::method(&x).
  • E0428: một tên được định nghĩa nhiều lần. Hai mục cùng tên trong một module, chứ không phải hai phương thức trait.
  • Tất cả các lỗi trình biên dịch: danh sách đầy đủ các trang lỗi.

Kiểm tra nhanh

1. Book cài đặt Summary và Label, và cả hai đều có fn describe(&self). Dòng nào gọi phiên bản của Label?

2. Cả hai trait đều có fn category() -> String không có self. Lời gọi nào chạy được?

3. Book có khối impl Book { fn describe(&self) ... } riêng và cũng cài đặt Summary, vốn cũng có describe. book.describe() làm gì?

Điểm: 0 / 3