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` here2 | use std::io::Result; // ❌ 第二个 `Result` | ^^^^^^^^^^^^^^^ `Result` reimported here | = note: `Result` must be defined only once in the type namespace of this modulehelp: 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,它们就全都消失了。
这个错误是什么意思
Section titled “这个错误是什么意思”rustc 指出了两行 use:
---那一行(第 1 行):“previous import of the typeResulthere”(之前在这里导入了类型Result)。它先出现,所以 Rust 保留的是它。^^^那一行(第 2 行):“Resultreimported here”(Result在这里被再次导入)。第二个导入想占用同一个名字。help:建议用as给第二个导入改名。
std::fmt::Result 和 std::io::Result 确实是两个不同的东西。fmt::Result 就是一个普通的 Result<(), fmt::Error>,给 Display 和 Debug 用。io::Result<T> 是 Result<T, io::Error>,给文件和网络操作用。名字一样,用途不同。
这就像你在手机里存了两个联系人都叫“妈”:一个是你妈妈,一个是你另一半的妈妈。你点“妈”的时候,手机得知道打给谁。于是你给其中一个加个字,或者干脆存成“妈(小芳)”和“妈(老李)”。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”在一个模块里,每个名字都只指向唯一一个项。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;。
修复 3:用 as _ 导入 trait
Section titled “修复 3:用 as _ 导入 trait”如果你导入某个 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` here2 | use std::io::Write; // ❌ 第二个 `Write` | ^^^^^^^^^^^^^^ `Write` reimported here | = note: `Write` must be defined only once in the type namespace of this modulehelp: 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` here2 | 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 种两个地方来的 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
- E0252 — Rust error codes index(英文):官方解释,包括用
as和用模块路径两种修复方法。 - 使用 as 关键字提供新的名称 — Rust 程序设计语言:
fmt::Result和io::Result的例子,用两种方法解决。 - Underscore imports — The Rust Reference(英文):
use Trait as _;怎样在不起名字的情况下引入方法。 - use 声明 — 通过例子学 Rust:导入和改名的简短可运行示例。
- Namespaces — The Rust Reference(英文):为什么 trait 和类型共用错误 note 里提到的类型命名空间。