跳转到内容

Rust 错误 E0255:the name is defined multiple times(导入与本地项重名)

E0255 的意思是:某一行 use 导入的名字,和你自己定义的东西重名了。最典型的情况:你在文件开头导入了 std::io::Error,后面又写了自己的 enum Error。Rust 不允许一个名字同时指两样东西,所以它停了下来。开始写自己的错误类型时,这个错误非常常见,而修复只需要改一小行。和 E0412 不同,这个错误码最近几个版本都没变过:rustc 1.97 报的仍然是 E0255。这个系列的其他错误,请看 Rust 编译错误详解。

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 { // ❌ 和第 1 行导入的名字相同
| ^^^^^^^^^^ `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;
| +++++++++++++
warning: unused import: `std::io::Error`
--> src/main.rs:1:5
|
1 | use std::io::Error;
| ^^^^^^^^^^^^^^

第一行的意思是“名字 Error 被定义了多次”。note 说明 Error 在这个模块的类型命名空间里只能定义一次;help 建议用 as 给导入换一个名字。

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

use std::io::Error;
enum Error { // ❌ 和第 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!("名字没问题"),
Err(Error::EmptyName) => println!("名字是空的"),
Err(Error::TooLong(n)) => println!("名字有 {} 个字母,最多 10 个", n),
}
}

rustc 指出了三处地方:

  1. --- 那一行(第 1 行):“previous import of the type Error here”(之前在这里导入了类型 Error)。use 这一行已经把名字 Error 放进了这个模块。
  2. ^^^ 那一行(第 3 行):“Error redefined here”(Error 在这里被重新定义)。你自己的 enum Error 想占用同一个名字。
  3. help: 给了一条出路:用 as 给导入起一个不同的本地名字。

下面那条 warning: unused import(未使用的导入)是个很有用的线索。它说明你的代码其实根本没用到 std::io::Error(这个程序里的每个 Error 指的都是你的枚举)。这种情况下,直接删掉第 1 行就行。如果两个都要用,就继续往下看。

note 里提到了类型命名空间(type namespace)。类型、trait 和模块共用一份名字列表;函数、常量和静态变量共用另一份。所以 use std::mem::swap; 再加上你自己的 fn swap,也是同一个错误,只不过发生在“值命名空间”(value namespace)里。

想象一个班里有两个都叫“小明”的同学。老师没法再只喊一声“小明”了,总得有人换个称呼,比如“高个儿小明”。as 给导入做的正是这件事。

模块里的一个名字只能指向唯一一个项。Rust 读到第 10 行的 Error::EmptyName 时,必须知道你说的是哪个 Error,而它拒绝去猜。如果它悄悄选了你的枚举,你的 use 那一行就形同虚设;如果它选了 io::Error,你的枚举就用不上了。不管选哪个,这种悄无声息的选择都会把错误藏起来。

所以 E0255 和 E0428(你自己的两个项重名)、E0252(两行 use 导入同一个名字)是兄弟。规则都一样:在一个模块里,一个名字只对应一样东西。use 的用法见模块。

当你在同一个文件里两个类型都要用时,选这个方法。这很常见:你自己的 Error 用来表示程序的业务规则,io::Error 用来处理文件和网络。给导入起一个能看出来源的别名。IoError 比 rustc 给的占位名 OtherError 好读:

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, "这个名字已被保留"));
}
Ok(())
}
fn main() {
println!("{:?}", check_name("Ferris"));
println!("{:?}", check_name(""));
println!("{:?}", check_name("Ferris the Crab"));
match save_name("root") {
Ok(()) => println!("已保存"),
Err(e) => println!("IO 错误:{}", e),
}
}

输出:

Ok(())
Err(EmptyName)
Err(TooLong(15))
IO 错误:这个名字已被保留
▶ 动手试一试

check_name 的参数保留英文,因为它会用 len() 数字节数,而一个汉字在 UTF-8 里占 3 个字节。

as 只在这个文件里改了名字。在其他地方,这个类型仍然叫 std::io::Error。

如果你希望每次用到 Error 时都能看出它来自哪里,就选这个方法。用 use std::io; 导入父模块,然后写 io::Error。你自己的 Error 保留短名字。对于 Error、Result 这类常见名字的类型,很多 Rust 程序员更喜欢这种写法:

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, "保留的名字"));
}
Ok(())
}
fn main() {
if let Err(Error::EmptyName) = check_name("") {
println!("名字是空的");
}
if let Err(e) = save_name("root") {
println!("IO 错误:{}", e);
}
}

输出:

名字是空的
IO 错误:保留的名字
▶ 动手试一试

如果你的类型会在很多文件里用到,或者会给别人用,就选这个方法。NameError 或 AppError 这样具体的名字,比再来一个 Error 清楚得多,而 use 那一行可以保持不变:

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("磁盘已满");
println!("{}", disk);
}

输出:

Err(TooLong(15))
磁盘已满
▶ 动手试一试

在更大的项目里,thiserror crate 能让这类错误枚举写起来短得多。参见自定义错误。

当 rustc 同时打印了 warning: unused import(就像上面的错误那样)时,选这个方法。这个导入是以前的代码留下来的,或者是编辑器自动加上的。删掉这一行,本页开头的程序就能照原样运行:

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!("名字没问题"),
Err(Error::EmptyName) => println!("名字是空的"),
Err(Error::TooLong(n)) => println!("名字有 {} 个字母,最多 10 个", n),
}
}

输出:

名字是空的
▶ 动手试一试

自己的函数用了标准库里的名字。 use std::mem::swap; 再加上 fn swap(a: &mut i32, b: &mut i32),会报 E0255,提示“previous import of the value swap here”,note 里说的是“value namespace”(值命名空间)。修复:把你的函数叫作 swap_values;如果你本来就打算自己写一个,就删掉这个导入。

在模块之间复制粘贴。 你把 struct User 挪进了 mod models,在文件开头加了 use models::User;,却忘了删掉 main.rs 里原来的 struct User。修复:删掉旧的那份,让 User 只指 models 里的那个。

trait 用了标准库里的名字。 use std::fmt::Display; 和你自己的 trait Display 也会这样冲突(“previous import of the trait Display”)。修复:给你的 trait 改个名字(Describe、Render),或者写 use std::fmt;,再用 fmt::Display。

顺便一提:像 use std::io::*; 这样的 glob 导入永远不会引发 E0255。你自己的项会悄悄胜过通过 * 引入的名字。这很方便,但也可能让你看不出用的到底是哪个 Error,所以明确写出导入通常更清楚。

  • E0252:两行 use 引入了同一个名字,比如 fmt::Result 和 io::Result。
  • E0428:你自己的两个项重名,常常是因为想重载一个函数。
  • E0592:同一个 impl 块里有两个同名方法。
  • E0432:use 这一行本身指向了不存在的东西。
  • E0425:相反的问题:你用到的名字在那里根本没有定义。
  • 所有编译错误:错误页面的完整列表。

小测验

1. 你的文件以 use std::fmt::Result; 开头,后面又定义了 enum Result { Pass, Fail }。你会得到哪个错误?

2. 同一个文件里,你既要用 std::io::Error,又要用自己的 enum Error。干净的修复是什么?

3. rustc 报了 E0255,同时还有 warning: unused import: std::io::Error。这个警告告诉你什么?

得分: 0 / 3