Lỗi Rust E0428: the name is defined multiple times (tên bị định nghĩa nhiều lần)
E0428 nghĩa là bạn đã định nghĩa hai thứ cùng tên ở cùng một chỗ: hai hàm tên area, hai struct tên User, hoặc một enum và một struct cùng tên Status. Lỗi này hay xảy ra khi bạn cố nạp chồng (overload) một hàm, như cách bạn làm trong Java, C# hay C++. Rust không có nạp chồng hàm, nhưng có những cách thay thế tốt, và trang này sẽ trình bày chúng. 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[E0428]: the name `area` is defined multiple times --> src/main.rs:5:1 |1 | fn area(width: f64, height: f64) -> f64 { | --------------------------------------- previous definition of the value `area` here...5 | fn area(radius: f64) -> f64 { // ❌ a second `area` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `area` redefined here | = note: `area` must be defined only once in the value namespace of this moduleTrình biên dịch báo tên area bị định nghĩa nhiều lần, chỉ ra định nghĩa trước đó và chỗ bị định nghĩa lại. Đây là chương trình gây ra lỗi:
fn area(width: f64, height: f64) -> f64 { width * height}
fn area(radius: f64) -> f64 { // ❌ a second `area` 3.14159 * radius * radius}
fn main() { println!("{}", area(2.0, 3.0)); println!("{}", area(1.0));}rustc còn in thêm một lỗi thứ hai cho chương trình này: E0061, “this function takes 2 arguments but 1 argument was supplied”, ở area(1.0). Đó chỉ là tác dụng phụ. Rust giữ lại area đầu tiên và bỏ qua cái thứ hai, nên lời gọi một đối số không còn khớp nữa. Sửa E0428 là E0061 biến mất theo.
Ý nghĩa
Phần tiêu đề “Ý nghĩa”rustc chỉ vào hai chỗ:
- Dòng
---(dòng 1): “previous definition of the valueareahere” (định nghĩa trước đó của giá trịareaở đây). Đây làareađầu tiên, cái mà Rust giữ lại. - Dòng
^^^(dòng 5): “arearedefined here” (areabị định nghĩa lại ở đây). Đây là cái thứ hai, và nó bị từ chối. - Phần
note:nói rằngareachỉ được định nghĩa một lần “in the value namespace of this module” (trong không gian tên giá trị của module này). Mỗi module giống như một ngăn kéo, và trong một ngăn kéo mọi cái tên phải là duy nhất.
Với hai struct, ghi chú sẽ nói “type namespace” (không gian tên kiểu). Rust giữ kiểu (struct, enum, trait, module) và giá trị (hàm, hằng số, static) trong hai danh sách riêng. Đó là lý do một mod shapes và một fn shapes có thể sống cạnh nhau, nhưng hai fn area hay hai struct User thì không.
Hãy nghĩ đến hai file cùng tên report.txt trong một thư mục. Máy tính của bạn từ chối file thứ hai, không phải vì nó có gì sai, mà vì khi đó report.txt không còn cho biết bạn muốn file nào.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Trong Rust, một cái tên trong module trỏ đến đúng một mục. Khi bạn viết area(...), Rust tìm hàm chỉ dựa vào tên, không dựa vào số lượng hay kiểu của đối số. Nhờ vậy code dễ đọc: chỉ có một area để tra, và thêm một hàm mới không bao giờ âm thầm thay đổi hàm mà một lời gọi cũ đang dùng.
Các ngôn ngữ có nạp chồng chọn phiên bản dựa trên đối số, và quy tắc trở nên rắc rối khi các con số tự chuyển đổi qua lại hoặc có thêm giá trị mặc định. Rust chọn không làm vậy. Khi bạn muốn “cùng một tên cho các đầu vào khác nhau”, bạn dùng trait hoặc generic, nơi lựa chọn hiện rõ trong các kiểu. Xem thêm về hàm ở Hàm.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Đặt cho mỗi cái một tên riêng
Phần tiêu đề “Cách sửa 1: Đặt cho mỗi cái một tên riêng”Hầu hết thời gian hãy chọn cách này. Hai hàm nhận đầu vào khác nhau thường làm những việc khác nhau, và cái tên có thể nói rõ việc nào. Nếu một trong hai là bản sao cũ bạn quên xóa, hãy xóa nó đi:
fn rectangle_area(width: f64, height: f64) -> f64 { width * height}
fn circle_area(radius: f64) -> f64 { 3.14159 * radius * radius}
fn main() { println!("{}", rectangle_area(2.0, 3.0)); println!("{}", circle_area(1.0));}Kết quả:
63.14159Thư viện chuẩn cũng làm như vậy: String::new(), String::with_capacity(10) và String::from("hi") là ba cái tên khác nhau cho ba cách tạo một String.
Cách sửa 2: Đặt chúng vào các module riêng
Phần tiêu đề “Cách sửa 2: Đặt chúng vào các module riêng”Chọn cách này khi cái tên ngắn thật sự là tên tốt nhất trong mỗi ngữ cảnh, như area cho mỗi hình, hoặc một User trong code cơ sở dữ liệu và một User khác trong code API. Mỗi module có các tên riêng, nên sự trùng lặp biến mất. Bên gọi viết tên module phía trước:
mod rectangle { pub fn area(width: f64, height: f64) -> f64 { width * height }}
mod circle { pub fn area(radius: f64) -> f64 { 3.14159 * radius * radius }}
fn main() { println!("{}", rectangle::area(2.0, 3.0)); println!("{}", circle::area(1.0));}Kết quả:
63.14159Đừng quên pub, nếu không lời gọi từ main sẽ cho ra E0603, “function is private”. Xem Module.
Cách sửa 3: Dùng trait thay cho nạp chồng
Phần tiêu đề “Cách sửa 3: Dùng trait thay cho nạp chồng”Chọn cách này khi bạn muốn một lời gọi dùng được cho nhiều kiểu, vốn là mục đích thường thấy của nạp chồng. Tạo một kiểu cho mỗi loại đầu vào, và cho chúng một trait chung. Giờ mỗi hình có area riêng, và Rust chọn đúng cái dựa vào kiểu của giá trị:
trait Shape { fn area(&self) -> f64;}
struct Rectangle { width: f64, height: f64,}
struct Circle { radius: f64,}
impl Shape for Rectangle { fn area(&self) -> f64 { self.width * self.height }}
impl Shape for Circle { fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }}
fn print_area<T: Shape>(shape: &T) { println!("Diện tích: {}", shape.area());}
fn main() { print_area(&Rectangle { width: 2.0, height: 3.0 }); print_area(&Circle { radius: 1.0 });}Kết quả:
Diện tích: 6Diện tích: 3.14159Cách này nhiều code hơn Cách sửa 1, nên nó đáng giá khi bạn có nhiều hình hoặc nhiều hàm (area, perimeter, draw) cùng theo một khuôn mẫu. Hàm generic print_area<T: Shape> dùng được cho mọi hình bạn thêm vào sau này. Xem Trait và Generic.
Với trường hợp “cùng một hàm, có hoặc không có thêm một đối số”, có một lựa chọn nhẹ nhàng hơn: nhận một Option, như fn greet(name: &str, title: Option<&str>), và gọi nó với None hoặc Some("Dr.").
Cách sửa 4: Đổi tên một import bằng use ... as ...
Phần tiêu đề “Cách sửa 4: Đổi tên một import bằng use ... as ...”Chọn cách này khi sự trùng lặp là giữa mục của chính bạn và một dòng use. Nói chính xác thì đó là một lỗi “anh em”, E0255, có cùng dòng tiêu đề:
error[E0255]: the name `Error` is defined multiple times --> src/main.rs:3:1 |1 | use std::io::Error; | -------------- previous import of the type `Error` here2 |3 | enum Error { | ^^^^^^^^^^ `Error` redefined here | = note: `Error` must be defined only once in the type namespace of this modulehelp: you can use `as` to change the binding name of the import |1 | use std::io::Error as OtherError; | +++++++++++++Hãy đặt cho import một biệt danh cục bộ bằng as. Chọn một cái tên nói rõ nó đến từ đâu, như IoError, thay vì tên tạm OtherError của rustc:
use std::io::Error as IoError;use std::io::ErrorKind;
#[derive(Debug)]enum Error { NotFound(String), Invalid(u32),}
fn main() { let mine = Error::NotFound(String::from("config.toml")); let other = Error::Invalid(42); let disk = IoError::new(ErrorKind::Other, "ổ đĩa đã đầy");
println!("{:?}", mine); println!("{:?}", other); println!("{}", disk);}Kết quả:
NotFound("config.toml")Invalid(42)ổ đĩa đã đầyHai dòng use trùng nhau, như use std::fmt::Result; và use std::io::Result;, cho ra một lỗi “anh em” thứ ba, E0252 (`Result` reimported here). Cách sửa vẫn vậy: use std::io::Result as IoResult;, hoặc import module (use std::io;) rồi viết io::Result. Đổi tên enum của chính bạn (AppError) cũng được.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Nạp chồng một phương thức trong khối impl. Hai fn new trong impl Timer cho ra một mã khác, E0592: duplicate definitions with name `new` . Cùng nguyên nhân, cùng cách sửa: đặt tên theo việc chúng làm, như Timer::new() và Timer::with_seconds(30). Xem Struct.
Copy-paste khi tái cấu trúc code. Bạn sao chép một hàm để sửa, hoặc dán một struct User từ file khác, rồi quên xóa cái cũ. Cũng hay gặp trong test: hai #[test] fn it_works() trong cùng một mod tests cho ra E0428. Cách sửa: xóa bản cũ, hoặc đổi tên test.
Biến thể enum bị trùng. enum Color { Red, Red } cũng cho ra E0428, chỉ vào Red thứ hai, với ghi chú “type namespace of this enum”. Cách sửa: xóa hoặc đổi tên biến thể bị trùng. Xem Enum.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0061: sai số đối số. Thường được in ngay sau E0428 khi bạn cố nạp chồng một hàm.
- E0425: vấn đề ngược lại: một cái tên được định nghĩa không lần nào ở chỗ bạn dùng nó.
- E0433: một đường dẫn như
circle::areamà phần đầu (module) không tìm thấy. - E0034: hai trait cho một kiểu các phương thức trùng tên; bạn cần nói rõ mình muốn cái nào.
- E0255 (một
usetrùng với một mục cục bộ), E0252 (hai dòngusetrùng nhau), E0592 (phương thức trùng lặp trong mộtimpl): những lỗi “anh em” được nêu ở trên. - 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
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Bạn viết fn total(a: u32) và fn total(a: u32, b: u32) trong cùng một file. Chuyện gì xảy ra?
2. mod report và fn report() có thể sống chung trong một module không?
3. File của bạn có use std::fmt::Result; và bạn định nghĩa enum Result. Cách sửa gọn gàng là gì?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0428 — Rust error codes index (tiếng Anh): giải thích chính thức, với ví dụ hai struct.
- Defining Modules to Control Scope and Privacy — The Rust Book (tiếng Anh): module cho mỗi phần code của bạn những cái tên riêng như thế nào.
- Providing New Names with the
asKeyword — The Rust Book (tiếng Anh): ví dụfmt::Resultvàio::Result, được giải quyết bằngas. - Traits — The Rust Book (tiếng Anh): chia sẻ hành vi giữa các kiểu, câu trả lời của Rust cho nạp chồng.
- Namespaces — The Rust Reference (tiếng Anh): quy tắc chính xác cho không gian tên kiểu và giá trị được nhắc trong ghi chú của lỗi.