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

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.

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 module

Trì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.

rustc chỉ vào hai chỗ:

  1. Dòng --- (dòng 1): “previous definition of the value area here” (định nghĩa trước đó của giá trị area ở đây). Đây là area đầu tiên, cái mà Rust giữ lại.
  2. Dòng ^^^ (dòng 5): “area redefined here” (area bị định nghĩa lại ở đây). Đây là cái thứ hai, và nó bị từ chối.
  3. Phần note: nói rằng area chỉ đượ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.

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 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ả:

6
3.14159
▶ Tự chạy thử

Thư 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ả:

6
3.14159
▶ Tự chạy thử

Đừng quên pub, nếu không lời gọi từ main sẽ cho ra E0603, “function is private”. Xem Module.

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: 6
Diện tích: 3.14159
▶ Tự chạy thử

Cá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` here
2 |
3 | enum Error {
| ^^^^^^^^^^ `Error` redefined here
|
= note: `Error` must be defined only once in the type namespace of this module
help: 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 đã đầy
▶ Tự chạy thử

Hai 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.

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.

  • 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::area mà 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 use trùng với một mục cục bộ), E0252 (hai dòng use trùng nhau), E0592 (phương thức trùng lặp trong một impl): 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

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