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` here2 |3 | enum Error { // ❌ 和第 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; | ^^^^^^^^^^^^^^第一行的意思是“名字 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), }}这个错误是什么意思
Section titled “这个错误是什么意思”rustc 指出了三处地方:
---那一行(第 1 行):“previous import of the typeErrorhere”(之前在这里导入了类型Error)。use这一行已经把名字Error放进了这个模块。^^^那一行(第 3 行):“Errorredefined here”(Error在这里被重新定义)。你自己的enum Error想占用同一个名字。help:给了一条出路:用as给导入起一个不同的本地名字。
下面那条 warning: unused import(未使用的导入)是个很有用的线索。它说明你的代码其实根本没用到 std::io::Error(这个程序里的每个 Error 指的都是你的枚举)。这种情况下,直接删掉第 1 行就行。如果两个都要用,就继续往下看。
note 里提到了类型命名空间(type namespace)。类型、trait 和模块共用一份名字列表;函数、常量和静态变量共用另一份。所以 use std::mem::swap; 再加上你自己的 fn swap,也是同一个错误,只不过发生在“值命名空间”(value namespace)里。
想象一个班里有两个都叫“小明”的同学。老师没法再只喊一声“小明”了,总得有人换个称呼,比如“高个儿小明”。as 给导入做的正是这件事。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”模块里的一个名字只能指向唯一一个项。Rust 读到第 10 行的 Error::EmptyName 时,必须知道你说的是哪个 Error,而它拒绝去猜。如果它悄悄选了你的枚举,你的 use 那一行就形同虚设;如果它选了 io::Error,你的枚举就用不上了。不管选哪个,这种悄无声息的选择都会把错误藏起来。
所以 E0255 和 E0428(你自己的两个项重名)、E0252(两行 use 导入同一个名字)是兄弟。规则都一样:在一个模块里,一个名字只对应一样东西。use 的用法见模块。
修复 1:用 as 给导入改名
Section titled “修复 1:用 as 给导入改名”当你在同一个文件里两个类型都要用时,选这个方法。这很常见:你自己的 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。
修复 2:导入模块,而不是类型
Section titled “修复 2:导入模块,而不是类型”如果你希望每次用到 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 错误:保留的名字修复 3:给你自己的项改名
Section titled “修复 3:给你自己的项改名”如果你的类型会在很多文件里用到,或者会给别人用,就选这个方法。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 能让这类错误枚举写起来短得多。参见自定义错误。
修复 4:删掉没用到的导入
Section titled “修复 4:删掉没用到的导入”当 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
- E0255 — Rust error codes index(英文):官方解释,示例里
use bar::foo;和fn foo冲突,用as或父模块路径解决。 - 使用 as 关键字提供新的名称 — Rust 程序设计语言:
as怎样给导入改名,用的正是两个Result类型的例子。 - Use declarations — The Rust Reference(英文):
use的所有写法,包括as、分组和 glob。 - Namespaces — The Rust Reference(英文):错误 note 里提到的类型命名空间和值命名空间。
- use 声明 — 通过例子学 Rust:导入和改名的简短可运行示例。