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

Lỗi Rust E0252: the name is defined multiple times (hai import trùng tên)

E0252 nghĩa là hai dòng use cùng đưa vào một cái tên. Ví dụ kinh điển là use std::fmt::Result; cộng với use std::io::Result;: hai kiểu khác nhau, cùng tên là Result. Những cặp khác là fmt::Error và io::Error, hoặc hai trait Write. Đây là lỗi rất phổ biến và dễ sửa, thường chỉ cần đổi một hai dòng use. rustc 1.97 vẫn báo nó bằng mã riêng là E0252. Trang này thuộc loạt bài về lỗi trình biên dịch.

error[E0252]: the name `Result` is defined multiple times
--> src/main.rs:2:5
|
1 | use std::fmt::Result;
| ---------------- previous import of the type `Result` here
2 | use std::io::Result; // ❌ a second `Result`
| ^^^^^^^^^^^^^^^ `Result` reimported here
|
= note: `Result` 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
|
2 | use std::io::Result as OtherResult; // ❌ a second `Result`
| ++++++++++++++

Thông báo của trình biên dịch luôn bằng tiếng Anh: “the name Result is defined multiple times” nghĩa là “tên Result 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::fmt::Result;
use std::io::Result; // ❌ a second `Result`
struct Temperature {
celsius: i32,
}
impl std::fmt::Display for Temperature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> Result<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(std::io::Error::other("not a number")),
}
}
fn main() {
match parse_temperature("21") {
Ok(t) => println!("{}", t),
Err(e) => println!("error: {}", e),
}
}

Sau lỗi này, rustc in thêm bốn lỗi nữa: E0107 (“type alias takes 0 generic arguments”, alias kiểu nhận 0 đối số generic), hai lỗi E0308 kiểu không khớp, và một E0277. Đừng chạy theo chúng. Rust giữ lại import đầu tiên, nên mọi Result trong file giờ đều mang nghĩa fmt::Result, và parse_temperature không còn hợp lý nữa. Sửa E0252 là tất cả đều biến mất.

rustc chỉ vào hai dòng use:

  1. Dòng --- (dòng 1): “previous import of the type Result here” (kiểu Result đã được import trước đó ở đây). Dòng này đến trước, nên đây là dòng Rust giữ lại.
  2. Dòng ^^^ (dòng 2): “Result reimported here” (Result bị import lại ở đây). Import thứ hai cố lấy cùng cái tên.
  3. Dòng help: đề xuất đổi tên import thứ hai bằng as.

std::fmt::Result và std::io::Result thật sự khác nhau. fmt::Result chỉ là Result<(), fmt::Error>, được Display và Debug dùng. io::Result<T> là Result<T, io::Error>, được dùng cho file và mạng. Cùng tên, khác việc.

Giống như bạn lưu hai số điện thoại cùng tên “Mẹ” trong máy: mẹ của bạn và mẹ của người yêu. Khi bạn bấm “Mẹ”, máy phải biết gọi cho ai. Bạn thêm một chữ vào một trong hai, hoặc lưu là “Mẹ (nhà)” và “Mẹ (Lan)”.

Trong một module, mỗi cái tên trỏ tới đúng một mục (item). Một dòng use không sao chép gì cả; nó thêm một tên ngắn cho một mục nằm ở chỗ khác. Hai dòng use thêm cùng một tên ngắn sẽ làm mọi Result phía sau trở nên mập mờ, nên Rust dừng ngay tại import, thay vì đoán ở từng chỗ dùng.

E0252 là một trong ba “anh em” có cùng dòng tiêu đề: E0255 là một dòng use xung đột với mục của chính bạn, còn E0428 là hai mục của chính bạn trùng tên. Quy tắc đằng sau cả ba là “trong mỗi module, một tên chỉ một thứ”. Xem Module.

Cách sửa 1: Import module và viết fmt:: và io::

Phần tiêu đề “Cách sửa 1: Import module và viết fmt:: và io::”

Phần lớn trường hợp, hãy chọn cách này, và luôn chọn nó cho Result và Error. Import các module cha, rồi viết tên module ở phía trước. Giờ mỗi chỗ dùng đều nói rõ đó là Result nào. The Rust Book khuyên dùng đúng phong cách này cho trường hợp này:

use std::fmt;
use std::io;
struct Temperature {
celsius: i32,
}
impl fmt::Display for Temperature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> io::Result<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(io::Error::other("không phải số")),
}
}
fn main() {
for input in ["21", " -4 ", "ấm"] {
match parse_temperature(input) {
Ok(t) => println!("{}", t),
Err(e) => println!("lỗi: {}", e),
}
}
}

Kết quả:

21 °C
-4 °C
lỗi: không phải số
▶ Tự chạy thử

Thêm một điểm cộng: Result<T, E> thông thường từ prelude vẫn dùng thoải mái, không cần import gì.

Chọn cách này khi bạn dùng một trong hai kiểu rất nhiều và muốn có tên ngắn cho nó. Đây là gợi ý của rustc. Hãy chọn một cái tên nói rõ nó đến từ đâu, như IoResult, thay vì OtherResult:

use std::fmt::Result;
use std::io::Result as IoResult;
struct Temperature {
celsius: i32,
}
impl std::fmt::Display for Temperature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> IoResult<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(std::io::Error::other("không phải số")),
}
}
fn main() {
match parse_temperature("21") {
Ok(t) => println!("{}", t),
Err(e) => println!("lỗi: {}", e),
}
}

Kết quả:

21 °C
▶ Tự chạy thử

Một Result trơn mà thật ra mang nghĩa fmt::Result có thể làm người đọc sau bất ngờ, nên cách sửa 1 thường rõ ràng hơn. as phát huy tác dụng với tên dài, như use std::collections::BTreeMap as Map;.

Chọn cách này cho các trait bạn chỉ import để gọi phương thức của chúng. Hai trait Write là ví dụ kinh điển: fmt::Write cho phép bạn write! vào một String, còn io::Write cho phép bạn write! ra stdout hoặc file. Import cả hai theo tên sẽ xung đột:

error[E0252]: the name `Write` is defined multiple times
--> src/main.rs:2:5
|
1 | use std::fmt::Write;
| --------------- previous import of the trait `Write` here
2 | use std::io::Write; // ❌ a second `Write`
| ^^^^^^^^^^^^^^ `Write` reimported here
|
= note: `Write` 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
|
2 | use std::io::Write as OtherWrite; // ❌ a second `Write`
| +++++++++++++

Chương trình bị lỗi:

use std::fmt::Write;
use std::io::Write; // ❌ a second `Write`
fn main() {
let mut report = String::new();
write!(report, "{} tests passed", 12).unwrap();
let mut out = std::io::stdout();
writeln!(out, "{}", report).unwrap();
}

Ở đây bạn không bao giờ tự viết chữ Write; bạn chỉ cần các phương thức của nó. as _ import một trait mà không đặt tên cho nó, nên không có gì để xung đột, và cả hai bộ phương thức vẫn dùng được:

use std::fmt::Write as _;
use std::io::Write as _;
fn main() {
let mut report = String::new();
write!(report, "{} bài test đã qua", 12).unwrap();
let mut out = std::io::stdout();
writeln!(out, "{}", report).unwrap();
}

Kết quả:

12 bài test đã qua
▶ Tự chạy thử

Nếu bạn cần gọi tên một trong hai, ví dụ trong fn log(w: &mut impl io::Write), hãy kết hợp với cách sửa 1: use std::io; và viết io::Write. Xem Trait.

Cách sửa 4: Xóa import trùng của cùng một mục

Phần tiêu đề “Cách sửa 4: Xóa import trùng của cùng một mục”

Chọn cách này khi cả hai dòng import chính xác cùng một thứ, điều thường xảy ra sau khi merge code hoặc khi trình soạn thảo thêm một import bạn đã có:

error[E0252]: the name `HashMap` is defined multiple times
--> src/main.rs:2:24
|
1 | use std::collections::HashMap;
| ------------------------- previous import of the type `HashMap` here
2 | use std::collections::{HashMap, HashSet}; // ❌ `HashMap` again
| ^^^^^^^--
| |
| `HashMap` reimported here
| help: remove unnecessary import

Chương trình bị lỗi:

use std::collections::HashMap;
use std::collections::{HashMap, HashSet}; // ❌ `HashMap` again
fn main() {
let mut stock: HashMap<&str, u32> = HashMap::new();
stock.insert("apples", 3);
let mut sold_out: HashSet<&str> = HashSet::new();
sold_out.insert("pears");
println!("apples: {}, sold out: {}", stock["apples"], sold_out.len());
}

Hãy để ý phần help khác hẳn: không có as, chỉ có “remove unnecessary import” (xóa import không cần thiết). Giữ lại một dòng:

use std::collections::{HashMap, HashSet};
fn main() {
let mut stock: HashMap<&str, u32> = HashMap::new();
stock.insert("táo", 3);
let mut sold_out: HashSet<&str> = HashSet::new();
sold_out.insert("lê");
println!("táo: {}, hết hàng: {}", stock["táo"], sold_out.len());
}

Kết quả:

táo: 3, hết hàng: 1
▶ Tự chạy thử

Xem HashMap và HashSet.

Error từ hai nơi. use std::fmt::Error; và use std::io::Error;, hoặc use std::error::Error; bên cạnh use anyhow::Error;. Cách sửa: use std::io; và viết io::Error, hoặc use anyhow::Error as AnyError;. Xem anyhow và thiserror.

Hai crate có kiểu trùng tên. use chrono::Duration; và use std::time::Duration; là một cặp hay gặp trong các dự án xử lý ngày giờ. Cách sửa: giữ tên ngắn cho một cái và viết kèm module cho cái kia, như chrono::Duration. Xem Ngày và giờ.

Import glob. use std::fmt::*; cộng với use std::io::*; không gây E0252 ở các dòng use. Rắc rối đến sau, ở Result đầu tiên, dưới dạng một lỗi không có mã: error: `Result` is ambiguous (Result mập mờ). Cách sửa: import tường minh những tên bạn cần, như trong cách sửa 1.

  • E0255: một dòng use xung đột với struct, enum hoặc hàm của chính bạn có cùng tên.
  • 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.
  • E0432: import không phân giải được, khi dòng use trỏ tới thứ không tồn tại.
  • E0034: hai trait trong phạm vi cho một kiểu những phương thức trùng tên.
  • E0308: kiểu không khớp, một trong các lỗi kéo theo sau một vụ xung đột Result.
  • Tất 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 cần fmt::Result và io::Result trong cùng một file. Cách import gọn gàng thường dùng là gì?

2. Bạn cần phương thức của cả std::fmt::Write lẫn std::io::Write, nhưng không bao giờ tự viết tên trait. Cách nào dùng được?

3. rustc nói HashMap bị import lại ở đây và gợi ý remove unnecessary import. Chuyện gì đã xảy ra?

Điểm: 0 / 3