跳转到内容

Rust 错误 E0252:the name is defined multiple times(两个导入同名)

E0252 的意思是:两行 use 引入了同一个名字。教科书式的例子是 use std::fmt::Result; 加上 use std::io::Result;:两个不同的类型,却都叫 Result。类似的还有 fmt::Error 和 io::Error,或者两个 Write trait。这个错误很常见,也很好改,通常只要改一两行 use。rustc 1.97 仍然用它自己的错误码 E0252 来报告。这个系列的其他错误,请看 Rust 编译错误详解。

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; // ❌ 第二个 `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; // ❌ 第二个 `Result`
| ++++++++++++++

第一行的意思是“名字 Result 被定义了多次”。help 建议用 as 给第二个导入换一个名字。

产生这个错误的程序是这样的:

use std::fmt::Result;
use std::io::Result; // ❌ 第二个 `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("不是数字")),
}
}
fn main() {
match parse_temperature("21") {
Ok(t) => println!("{}", t),
Err(e) => println!("错误:{}", e),
}
}

rustc 在这个错误之后还会再打印四个错误:E0107(“type alias takes 0 generic arguments”,类型别名不接受泛型参数)、两个 E0308 类型不匹配,以及一个 E0277。别去追它们。Rust 保留了第一个导入,所以文件里的每个 Result 现在都指 fmt::Result,parse_temperature 也就说不通了。修好 E0252,它们就全都消失了。

rustc 指出了两行 use:

  1. --- 那一行(第 1 行):“previous import of the type Result here”(之前在这里导入了类型 Result)。它先出现,所以 Rust 保留的是它。
  2. ^^^ 那一行(第 2 行):“Result reimported here”(Result 在这里被再次导入)。第二个导入想占用同一个名字。
  3. help: 建议用 as 给第二个导入改名。

std::fmt::Result 和 std::io::Result 确实是两个不同的东西。fmt::Result 就是一个普通的 Result<(), fmt::Error>,给 Display 和 Debug 用。io::Result<T> 是 Result<T, io::Error>,给文件和网络操作用。名字一样,用途不同。

这就像你在手机里存了两个联系人都叫“妈”:一个是你妈妈,一个是你另一半的妈妈。你点“妈”的时候,手机得知道打给谁。于是你给其中一个加个字,或者干脆存成“妈(小芳)”和“妈(老李)”。

在一个模块里,每个名字都只指向唯一一个项。use 这一行并不复制任何东西,它只是给住在别处的项加了一个短名字。如果两行 use 加了同一个短名字,后面每一个 Result 都会有歧义,所以 Rust 直接在导入处停下,而不是在每次使用时去猜。

E0252 是三兄弟之一,它们的标题一模一样:E0255 是 use 和你自己的项冲突,E0428 是你自己的两个项冲突。三者背后的规则都是“在一个模块里,一个名字只对应一样东西”。参见模块。

修复 1:导入模块,写 fmt:: 和 io::

Section titled “修复 1:导入模块,写 fmt:: 和 io::”

大多数时候选这个方法,遇到 Result 和 Error 时更是如此。导入父模块,然后在前面写上模块名。这样每次使用时都能看出是哪个 Result。《Rust 程序设计语言》正是在这种情况下推荐这种写法:

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("不是数字")),
}
}
fn main() {
for input in ["21", " -4 ", "暖和"] {
match parse_temperature(input) {
Ok(t) => println!("{}", t),
Err(e) => println!("错误:{}", e),
}
}
}

输出:

21 °C
-4 °C
错误:不是数字
▶ 动手试一试

额外的好处:prelude 里那个普通的 Result<T, E> 依然可以直接用,不需要任何导入。

修复 2:用 as 给其中一个导入改名

Section titled “修复 2:用 as 给其中一个导入改名”

如果你经常用两个类型中的某一个,想给它一个短名字,就选这个方法。这也是 rustc 给的建议。挑一个能看出来源的名字,比如 IoResult,而不是 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("不是数字")),
}
}
fn main() {
match parse_temperature("21") {
Ok(t) => println!("{}", t),
Err(e) => println!("错误:{}", e),
}
}

输出:

21 °C
▶ 动手试一试

一个看起来普通、实际上指 fmt::Result 的 Result,可能会让下一个读代码的人吃一惊,所以修复 1 通常更清楚。as 更适合用在很长的名字上,比如 use std::collections::BTreeMap as Map;。

如果你导入某个 trait 只是为了调用它的方法,就选这个方法。最经典的是两个 Write trait:fmt::Write 让你能用 write! 往 String 里写,io::Write 让你能用 write! 往标准输出或文件里写。按名字同时导入两者,就会冲突:

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; // ❌ 第二个 `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; // ❌ 第二个 `Write`
| +++++++++++++

出错的程序:

use std::fmt::Write;
use std::io::Write; // ❌ 第二个 `Write`
fn main() {
let mut report = String::new();
write!(report, "{} 个测试通过", 12).unwrap();
let mut out = std::io::stdout();
writeln!(out, "{}", report).unwrap();
}

这里你自己从来没写过 Write 这个名字,你只需要它的方法。as _ 导入 trait 时不给它起名字,所以不会冲突,两套方法也都照样能用:

use std::fmt::Write as _;
use std::io::Write as _;
fn main() {
let mut report = String::new();
write!(report, "{} 个测试通过", 12).unwrap();
let mut out = std::io::stdout();
writeln!(out, "{}", report).unwrap();
}

输出:

12 个测试通过
▶ 动手试一试

如果你确实需要写出其中一个的名字,比如 fn log(w: &mut impl io::Write),就把它和修复 1 结合起来:use std::io;,然后写 io::Write。参见 trait。

修复 4:删掉重复导入的同一个项

Section titled “修复 4:删掉重复导入的同一个项”

如果两行导入的是完全相同的东西,就选这个方法。合并代码之后,或者编辑器又自动加了一个你已经有的导入时,经常会这样:

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`
| ^^^^^^^--
| |
| `HashMap` reimported here
| help: remove unnecessary import

出错的程序:

use std::collections::HashMap;
use std::collections::{HashMap, HashSet}; // ❌ 又一个 `HashMap`
fn main() {
let mut stock: HashMap<&str, u32> = HashMap::new();
stock.insert("苹果", 3);
let mut sold_out: HashSet<&str> = HashSet::new();
sold_out.insert("梨");
println!("苹果:{},售罄:{} 种", stock["苹果"], sold_out.len());
}

注意这次的 help 不一样了:没有 as,只有“remove unnecessary import”(删掉多余的导入)。保留一行就好:

use std::collections::{HashMap, HashSet};
fn main() {
let mut stock: HashMap<&str, u32> = HashMap::new();
stock.insert("苹果", 3);
let mut sold_out: HashSet<&str> = HashSet::new();
sold_out.insert("梨");
println!("苹果:{},售罄:{} 种", stock["苹果"], sold_out.len());
}

输出:

苹果:3,售罄:1 种
▶ 动手试一试

参见 HashMap 和 HashSet。

两个地方来的 Error。 use std::fmt::Error; 和 use std::io::Error;,或者 use std::error::Error; 旁边又有 use anyhow::Error;。修复:use std::io; 然后写 io::Error,或者写 use anyhow::Error as AnyError;。参见 anyhow 和 thiserror。

两个 crate 里有同名类型。 在处理日期的项目里,use chrono::Duration; 和 use std::time::Duration; 经常一起出现。修复:只给一个保留短名字,另一个写出模块,比如 chrono::Duration。参见日期和时间。

glob 导入。 use std::fmt::*; 加上 use std::io::*;,在 use 这两行并不会报 E0252。麻烦出在后面第一次用 Result 的地方,那是一个没有错误码的错误:error: `Result` is ambiguous(Result 有歧义)。修复:像修复 1 那样,明确导入你需要的名字。

  • E0255:一行 use 和你自己的同名结构体、枚举或函数冲突。
  • E0428:你自己的两个项重名,常常是因为想重载一个函数。
  • E0432:无法解析的导入,use 这一行指向了不存在的东西。
  • E0034:作用域里的两个 trait 给同一个类型提供了同名方法。
  • E0308:类型不匹配,Result 冲突之后接着出现的错误之一。
  • 所有编译错误:错误页面的完整列表。

小测验

1. 同一个文件里你需要 fmt::Result 和 io::Result。通常干净的导入方式是哪个?

2. 你需要 std::fmt::Write 和 std::io::Write 两者的方法,但自己从来不写这两个 trait 的名字。哪种做法可行?

3. rustc 说 HashMap 在这里被再次导入,并建议 remove unnecessary import。发生了什么?

得分: 0 / 3