Lỗi Rust E0592: duplicate definitions with name X (phương thức trùng tên)
E0592 nghĩa là: cùng một kiểu có hai phương thức, hàm liên kết (associated function) hoặc hằng số trùng tên, được viết trong các khối impl. Một kiểu có thể có bao nhiêu khối impl cũng được, nhưng tất cả dùng chung một danh sách tên, nên mỗi tên chỉ được dùng một lần. Lỗi này thường đến từ việc copy-paste, một lần merge, hoặc một khối impl thứ hai nằm ở file khác. Cách sửa là giữ lại một cái, hoặc đặt tên mới cho cái kia. (Trang này thuộc loạt bài Giải thích các lỗi trình biên dịch Rust.)
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”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`Thông báo của trình biên dịch luôn bằng tiếng Anh: “duplicate definitions with name reset” nghĩa là “có nhiều định nghĩa trùng tên reset”. Đây là chương trình gây ra lỗi đó (chú thích được giữ bằng tiếng Anh vì nó xuất hiện nguyên văn trong thông báo lỗi):
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);}rustc cũng báo E0034, “multiple applicable items in scope” (có nhiều mục phù hợp trong phạm vi), tại lời gọi c.reset(), vì nó không biết bạn muốn gọi reset nào trong hai cái. Đó là lỗi kéo theo: sửa E0592 là E0034 cũng biến mất.
Ý nghĩa
Phần tiêu đề “Ý nghĩa”Dòng tiêu đề cho biết cái tên bị định nghĩa hai lần (reset). Dấu ^^^ đánh dấu một định nghĩa, còn dòng --- đánh dấu “định nghĩa còn lại” (other definition). rustc hiện số dòng của cả hai, nên bạn có thể nhảy thẳng tới đó. Khi chúng nằm xa nhau, dấu ... ở giữa chỉ có nghĩa là “đã bỏ qua vài dòng”.
Việc hai reset nằm trong hai khối impl Counter khác nhau, hay thậm chí ở hai file khác nhau trong crate, không quan trọng. Với Rust, tất cả đều là phương thức của cùng một kiểu, nên Counter::reset phải chỉ đúng một hàm. Quy tắc này cũng áp dụng cho hằng số liên kết: hai const MAX: u32 cho một kiểu cũng là E0592.
Giống như danh bạ điện thoại có hai mục cùng tên “Mẹ”. Khi bạn nói “gọi cho Mẹ”, máy nên quay số nào?
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Chia phương thức ra nhiều khối impl là được phép và thường hữu ích: một khối cho hàm tạo, một khối cho getter, một khối nằm sau #[cfg(test)]. Xem Thêm hành vi bằng phương thức. Nhưng khi bạn gọi c.reset() hoặc Counter::reset(&mut c), Rust phải tìm ra một hàm. Nếu có hai, hàm nào chạy sẽ phụ thuộc vào những chi tiết mà không ai nhìn thấy khi đọc lời gọi.
Vì vậy Rust từ chối ngay tại chỗ định nghĩa, chứ không chỉ tại lời gọi. Điều này bắt được một bug có thật: với một phương thức bị copy-paste, bạn có thể sửa lỗi ở một bản, trong khi bản kia vẫn tiếp tục chạy. Rust đảm bảo lúc nào cũng chỉ có một phiên bản để sửa.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Xóa bản trùng và giữ lại một định nghĩa
Phần tiêu đề “Cách sửa 1: Xóa bản trùng và giữ lại một định nghĩa”Phần lớn trường hợp, hãy chọn cách này. Nếu hai bản làm cùng một việc (copy-paste, một lần merge lộn xộn, một phương thức bạn đã chuyển đi nhưng quên xóa), hãy xóa một bản. Trước khi xóa, hãy so sánh hai phần thân: nếu một bản có bản sửa lỗi mà bản kia không có, hãy giữ bản đó. Gộp các khối impl lại thành một cũng giúp bạn dễ nhận ra bản trùng hơn về sau.
struct Counter { count: u32,}
impl Counter { fn new() -> Self { Counter { count: 0 } }
fn increment(&mut self) { self.count += 1; }
fn reset(&mut self) { // ✅ `Counter` chỉ có một `reset` self.count = 0; }}
fn main() { let mut c = Counter::new(); c.increment(); c.increment(); println!("trước khi reset: {}", c.count); c.reset(); println!("sau khi reset: {}", c.count);}Kết quả:
trước khi reset: 2sau khi reset: 0Nếu bạn không tìm thấy định nghĩa thứ hai, hãy tìm trong toàn bộ dự án: grep -rn "fn reset" src/. Một khối impl Counter có thể nằm ở bất kỳ module nào trong crate của bạn.
Cách sửa 2: Đổi tên một trong hai
Phần tiêu đề “Cách sửa 2: Đổi tên một trong hai”Chọn cách này khi hai định nghĩa làm những việc khác nhau. Rust không có nạp chồng hàm (overloading): bạn không thể có hai phương thức cùng tên nhưng khác tham số, như reset() và reset(start: u32). Trường hợp đó cũng là E0592. Hãy đặt cho mỗi cái một tên nói đúng việc nó làm:
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) { // ✅ hành vi khác thì tên khác self.count = start; }}
fn main() { let mut c = Counter::new(); c.reset_to(10); println!("sau reset_to(10): {}", c.count); c.reset(); println!("sau reset(): {}", c.count);}Kết quả:
sau reset_to(10): 10sau reset(): 0Các kiểu đặt tên quen thuộc trong Rust giúp ích ở đây: new và with_capacity, from_str và from_bytes, get và get_mut. Nếu bạn thật sự muốn một tên cho nhiều kiểu đối số, hãy dùng tham số generic hoặc trait, như fn reset_to(&mut self, start: impl Into<u32>). Xem Generic.
Cách sửa 3: Tránh để các khối impl generic chồng lên nhau
Phần tiêu đề “Cách sửa 3: Tránh để các khối impl generic chồng lên nhau”Chọn cách này khi một trong hai định nghĩa nằm trong một khối generic như impl<T> Wrapper<T>. Khối đó cũng bao gồm Wrapper<i32>, nên một khối impl Wrapper<i32> thứ hai có phương thức cùng tên là bản trùng:
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`Chương trình đằng sau lỗi này:
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 không cho một impl cụ thể ghi đè một impl generic (tính năng đó, gọi là specialization, chưa ổn định). Có hai lối ra tốt: chỉ viết khối impl cho các kiểu cụ thể, để chúng không thể chồng nhau, hoặc đặt hành vi vào một trait và cài đặt nó một lần cho mỗi kiểu:
struct Wrapper<T>(T);
// Cách A: các khối impl cho kiểu cụ thể không bao giờ chồng nhauimpl Wrapper<i32> { fn describe(&self) -> String { format!("số {}", self.0) }}
impl Wrapper<&str> { fn describe(&self) -> String { format!("văn bản \"{}\"", self.0) }}
// Cách B: một trait, cài đặt một lần cho mỗi kiểutrait Label { fn label(&self) -> String;}
impl Label for i32 { fn label(&self) -> String { format!("số nguyên {}", self) }}
impl Label for bool { fn label(&self) -> String { format!("cờ {}", self) }}
impl<T: Label> Wrapper<T> { fn label(&self) -> String { self.0.label() // một phương thức generic, phần riêng cho từng kiểu nằm trong trait }}
fn main() { println!("{}", Wrapper(5).describe()); println!("{}", Wrapper("hi").describe()); println!("{}", Wrapper(7).label()); println!("{}", Wrapper(true).label());}Kết quả:
số 5văn bản "hi"số nguyên 7cờ truePhiên bản dùng trait thường là thiết kế tốt hơn về lâu dài: hỗ trợ thêm một kiểu mới chỉ cần thêm một impl Label for .... Xem Trait.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Một hàm hỗ trợ cho test trong khối impl thứ hai. Bạn thêm impl Counter { fn new() -> Self { ... } } bên trong #[cfg(test)] mod tests để tạo giá trị cho test, nhưng Counter::new đã tồn tại. Khối trong test vẫn là một impl Counter. Cách sửa: đặt tên khác như fn for_test(), hoặc dùng luôn new thật. Xem Kiểm thử.
Sau một lần merge hoặc refactor. Hai nhánh cùng thêm một fn len(&self), hoặc một phương thức được chuyển sang file mới nhưng bản cũ vẫn còn. rustc hiện số dòng của cả hai; hãy giữ bản tốt hơn.
Phương thức trait trùng tên thì không sao. impl Counter { fn reset(&mut self) } cùng với impl Resettable for Counter { fn reset(&mut self) } không phải E0592: một cái là phương thức riêng của kiểu, cái kia thuộc về trait, và c.reset() gọi phương thức riêng của kiểu. Hai trait có phương thức trùng tên cũng biên dịch được, nhưng gọi bằng c.reset() sẽ gây ra E0034; hãy sửa bằng Resettable::reset(&mut c).
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0034: multiple applicable items in scope: lỗi kéo theo tại chỗ gọi, và cũng là lỗi khi hai trait có phương thức trùng tên.
- E0428: the name is defined multiple times: cùng sai sót đó với các mục (item) trong một module, như hai
struct Counterhoặc hai hàm tự dofn main. - E0599: no method named x found: vấn đề ngược lại, một phương thức hoàn toàn không tồn tại.
- E0255: tên đã được một import dùng và E0252: tên bị import hai lần: tên trùng do
usegây ra. - Tất cả lỗi trình biên dịch
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Một kiểu có hai khối impl. Cả hai có thể cùng chứa một phương thức tên reset không?
2. Bạn muốn có area() và area(scale: f64). Trong Rust bạn làm gì?
3. Vì sao impl<T> Wrapper<T> và impl Wrapper<i32> xung đột khi cả hai định nghĩa describe?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0592 — Rust error codes index (tiếng Anh): phần giải thích chính thức của lỗi này.
- Method Syntax — The Rust Book (tiếng Anh): phương thức, hàm liên kết và mục “Multiple
implBlocks”. - Generic Data Types — The Rust Book (tiếng Anh):
impl<T> Point<T>đặt cạnhimpl Point<f32>, và mối liên hệ giữa chúng. - Implementations — The Rust Reference (tiếng Anh): các quy tắc chính xác cho khối
implriêng của kiểu và các mục liên kết của chúng. - E0034 — Rust error codes index (tiếng Anh): trang chính thức cho lỗi kéo theo “multiple applicable items”.