Lỗi Rust E0255: the name is defined multiple times (import trùng mục cục bộ)
E0255 nghĩa là một dòng use và một thứ do chính bạn định nghĩa có cùng tên. Trường hợp kinh điển: bạn import std::io::Error ở đầu file, rồi sau đó viết enum Error của riêng mình. Rust không thể để một cái tên mang hai nghĩa, nên nó dừng lại. Đây là lỗi rất hay gặp khi bạn bắt đầu viết kiểu lỗi của riêng mình, và cách sửa chỉ là một dòng ngắn. Khác với E0412, mã lỗi này không thay đổi trong các bản phát hành gần đây: rustc 1.97 vẫn báo nó là E0255. Trang này thuộc loạt bài về lỗi trình biên dịch.
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”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 { // ❌ same name as the import on line 1 | ^^^^^^^^^^ `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; | +++++++++++++
warning: unused import: `std::io::Error` --> src/main.rs:1:5 |1 | use std::io::Error; | ^^^^^^^^^^^^^^Thông báo của trình biên dịch luôn bằng tiếng Anh: “the name Error is defined multiple times” nghĩa là “tên Error bị định nghĩa nhiều lần”. Đâ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):
use std::io::Error;
enum Error { // ❌ same name as the import on line 1 EmptyName, TooLong(usize),}
fn check_name(name: &str) -> Result<(), Error> { if name.is_empty() { return Err(Error::EmptyName); } if name.len() > 10 { return Err(Error::TooLong(name.len())); } Ok(())}
fn main() { match check_name("") { Ok(()) => println!("name is fine"), Err(Error::EmptyName) => println!("name is empty"), Err(Error::TooLong(n)) => println!("name has {} letters, max is 10", n), }}Ý nghĩa
Phần tiêu đề “Ý nghĩa”rustc chỉ ra ba thứ:
- Dòng
---(dòng 1): “previous import of the typeErrorhere” (kiểuErrorđã được import trước đó ở đây). Dònguseđã đưa một cái tênErrorvào module này. - Dòng
^^^(dòng 3): “Errorredefined here” (Errorbị định nghĩa lại ở đây).enum Errorcủa bạn cố lấy cùng cái tên đó. - Dòng
help:đưa ra một lối thoát: đặt cho import một tên cục bộ khác bằngas.
Dòng warning: unused import bên dưới là một manh mối hữu ích. Nó nghĩa là không có chỗ nào trong code thật sự dùng std::io::Error (mọi Error trong chương trình này đều là enum của bạn). Trong trường hợp đó, bạn chỉ cần xóa dòng 1. Nếu bạn thật sự cần cả hai, hãy đọc tiếp.
Dòng note nhắc tới type namespace (không gian tên của kiểu). Kiểu, trait và module dùng chung một danh sách tên; hàm, hằng số và static dùng chung một danh sách khác. Vì vậy use std::mem::swap; cộng với fn swap của riêng bạn cũng là lỗi này, nhưng ở “value namespace” (không gian tên của giá trị).
Hãy hình dung một lớp học có hai bạn cùng tên An. Cô giáo không thể chỉ gọi “An” được nữa. Phải có một người mang biệt danh, như “An B.”, và đó chính xác là thứ as làm cho một import.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Trong một module, một cái tên trỏ tới đúng một mục (item). Khi Rust đọc Error::EmptyName ở dòng 10, nó phải biết bạn muốn nói Error nào, và nó không chịu đoán. Nếu nó lặng lẽ chọn enum của bạn, dòng use sẽ không có tác dụng gì; nếu nó chọn io::Error, enum của bạn sẽ không thể dùng tới. Dù chọn cách nào, một lựa chọn âm thầm cũng sẽ che giấu một sai sót.
Đó là lý do E0255 là “anh em” của E0428 (hai mục của chính bạn trùng tên) và E0252 (hai dòng use trùng tên). Quy tắc giống nhau: trong mỗi module, một tên chỉ một thứ. Xem Module để biết use hoạt động thế nào.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Đổi tên import bằng as
Phần tiêu đề “Cách sửa 1: Đổi tên import bằng as”Chọn cách này khi bạn cần cả hai kiểu trong cùng một file, điều này khá phổ biến: Error của riêng bạn cho các quy tắc của ứng dụng, và io::Error cho file và mạng. Hãy đặt cho import một biệt danh nói rõ nó đến từ đâu. IoError dễ đọc hơn cái tên tạm OtherError của rustc:
use std::io::Error as IoError;use std::io::ErrorKind;
#[derive(Debug)]enum Error { EmptyName, TooLong(usize),}
fn check_name(name: &str) -> Result<(), Error> { if name.is_empty() { return Err(Error::EmptyName); } if name.len() > 10 { return Err(Error::TooLong(name.len())); } Ok(())}
fn save_name(name: &str) -> Result<(), IoError> { if name == "root" { return Err(IoError::new(ErrorKind::PermissionDenied, "tên này đã được dành riêng")); } Ok(())}
fn main() { println!("{:?}", check_name("Ferris")); println!("{:?}", check_name("")); println!("{:?}", check_name("Ferris the Crab")); match save_name("root") { Ok(()) => println!("đã lưu"), Err(e) => println!("lỗi io: {}", e), }}Kết quả:
Ok(())Err(EmptyName)Err(TooLong(15))lỗi io: tên này đã được dành riêngTên "Ferris the Crab" được giữ bằng tiếng Anh vì kết quả phụ thuộc vào .len(), tức số byte (chữ tiếng Việt có dấu chiếm 2–3 byte). as chỉ đổi tên trong file này. Ở mọi nơi khác, kiểu đó vẫn là std::io::Error.
Cách sửa 2: Import module, không import kiểu
Phần tiêu đề “Cách sửa 2: Import module, không import kiểu”Chọn cách này khi bạn muốn mỗi Error cho thấy nó đến từ đâu ở mọi chỗ dùng. Import module cha bằng use std::io; và viết io::Error. Error của riêng bạn giữ được tên ngắn. Nhiều lập trình viên Rust thích phong cách này cho các kiểu có tên phổ biến như Error và Result:
use std::io;
enum Error { EmptyName,}
fn check_name(name: &str) -> Result<(), Error> { if name.is_empty() { return Err(Error::EmptyName); } Ok(())}
fn save_name(name: &str) -> Result<(), io::Error> { if name == "root" { return Err(io::Error::new(io::ErrorKind::PermissionDenied, "tên dành riêng")); } Ok(())}
fn main() { if let Err(Error::EmptyName) = check_name("") { println!("tên đang trống"); } if let Err(e) = save_name("root") { println!("lỗi io: {}", e); }}Kết quả:
tên đang trốnglỗi io: tên dành riêngCách sửa 3: Đổi tên mục của chính bạn
Phần tiêu đề “Cách sửa 3: Đổi tên mục của chính bạn”Chọn cách này khi kiểu của bạn sẽ được dùng ở nhiều file, hoặc bởi người khác. Một tên cụ thể như NameError hay AppError rõ ràng hơn một Error thứ hai, và dòng use có thể giữ nguyên:
use std::io::Error;
#[derive(Debug)]enum NameError { EmptyName, TooLong(usize),}
fn check_name(name: &str) -> Result<(), NameError> { if name.is_empty() { return Err(NameError::EmptyName); } if name.len() > 10 { return Err(NameError::TooLong(name.len())); } Ok(())}
fn main() { println!("{:?}", check_name("Ferris the Crab")); let disk = Error::other("ổ đĩa đã đầy"); println!("{}", disk);}Kết quả:
Err(TooLong(15))ổ đĩa đã đầyVới dự án lớn hơn, crate thiserror giúp viết những enum lỗi như thế này ngắn hơn nhiều. Xem Lỗi tùy chỉnh.
Cách sửa 4: Xóa import bạn không dùng
Phần tiêu đề “Cách sửa 4: Xóa import bạn không dùng”Chọn cách này khi rustc cũng in ra warning: unused import, như trong lỗi ở trên. Import đó là phần sót lại từ code cũ, hoặc do trình soạn thảo tự thêm vào. Hãy xóa dòng đó, và chương trình ở đầu trang chạy đúng như đã viết:
enum Error { EmptyName, TooLong(usize),}
fn check_name(name: &str) -> Result<(), Error> { if name.is_empty() { return Err(Error::EmptyName); } if name.len() > 10 { return Err(Error::TooLong(name.len())); } Ok(())}
fn main() { match check_name("") { Ok(()) => println!("tên hợp lệ"), Err(Error::EmptyName) => println!("tên đang trống"), Err(Error::TooLong(n)) => println!("tên dài {} byte, tối đa là 10", n), }}Kết quả:
tên đang trốngThường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Hàm của bạn trùng tên với hàm trong std. use std::mem::swap; cộng với fn swap(a: &mut i32, b: &mut i32) gây ra E0255 với dòng “previous import of the value swap here” và note “value namespace”. Cách sửa: đặt tên hàm của bạn là swap_values, hoặc bỏ import nếu bạn định tự viết hàm.
Copy-paste giữa các module. Bạn chuyển struct User vào mod models, thêm use models::User; ở đầu file, nhưng quên xóa struct User cũ trong main.rs. Cách sửa: xóa bản cũ, để User chỉ còn nghĩa là cái trong models.
Trait trùng tên với trait trong std. use std::fmt::Display; và trait Display của riêng bạn xung đột theo đúng cách đó (“previous import of the trait Display”). Cách sửa: đổi tên trait của bạn (Describe, Render), hoặc viết use std::fmt; và fmt::Display.
Nên biết: một import kiểu glob như use std::io::*; không bao giờ gây ra E0255. Các mục của chính bạn lặng lẽ thắng những tên đến qua *. Điều đó tiện, nhưng cũng có thể che mất việc bạn đang dùng Error nào, nên import tường minh thường rõ ràng hơn.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0252: hai dòng
useđưa vào cùng một tên, nhưfmt::Resultvàio::Result. - E0428: hai mục của chính bạn trùng tên, thường do cố nạp chồng (overload) một hàm.
- E0592: hai phương thức trùng tên trong khối
impl. - E0432: chính dòng
usetrỏ tới thứ không tồn tại. - E0425: vấn đề ngược lại: một tên hoàn toàn chưa được định nghĩa ở chỗ bạn dùng.
- Tất 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. File của bạn bắt đầu bằng use std::fmt::Result; và sau đó định nghĩa enum Result { Pass, Fail }. Bạn gặp lỗi nào?
2. Bạn cần cả std::io::Error lẫn enum Error của riêng mình trong một file. Cách sửa gọn gàng là gì?
3. rustc báo E0255 và cả warning: unused import: std::io::Error. Cảnh báo đó cho bạn biết gì?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0255 — Rust error codes index (tiếng Anh): phần giải thích chính thức, với một
use bar::foo;xung đột vớifn foo, được sửa bằngashoặc đường dẫn module cha. - Providing New Names with the
asKeyword — The Rust Book (tiếng Anh): cáchasđổi tên một import, với ví dụ hai kiểuResult. - Use declarations — The Rust Reference (tiếng Anh): mọi dạng của
use, kể cảas, nhóm và glob. - Namespaces — The Rust Reference (tiếng Anh): type namespace và value namespace được nhắc tới trong note của lỗi.
- The
usedeclaration — Rust by Example (tiếng Anh): các ví dụ ngắn, chạy được về import và đổi tên.