Rust 错误 E0428:the name is defined multiple times(名称重复定义)
E0428 的意思是:你在同一个地方定义了两个同名的东西:两个叫 area 的函数、两个叫 User 的结构体,或者一个枚举和一个结构体都叫 Status。这通常发生在你想像 Java、C# 或 C++ 那样重载(overload)函数的时候。Rust 没有函数重载,但它有很好的替代办法,本页会一一介绍。这个系列的其他错误,请看 Rust 编译错误详解。
error[E0428]: the name `area` is defined multiple times --> src/main.rs:5:1 |1 | fn area(width: f64, height: f64) -> f64 { | --------------------------------------- previous definition of the value `area` here...5 | fn area(radius: f64) -> f64 { // ❌ a second `area` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `area` redefined here | = note: `area` must be defined only once in the value namespace of this module第一行的意思是“名字 area 被定义了多次”。
产生这个错误的程序是这样的:
fn area(width: f64, height: f64) -> f64 { width * height}
fn area(radius: f64) -> f64 { // ❌ a second `area` 3.14159 * radius * radius}
fn main() { println!("{}", area(2.0, 3.0)); println!("{}", area(1.0));}对这个程序,rustc 还会在 area(1.0) 上再报一个错误:E0061,“this function takes 2 arguments but 1 argument was supplied”(这个函数需要 2 个参数,却只给了 1 个)。这只是连带效应。Rust 保留了第一个 area,忽略了第二个,所以只传一个参数的调用就对不上了。修好 E0428,E0061 也会跟着消失。
这个错误是什么意思
Section titled “这个错误是什么意思”rustc 指出了两个位置:
---那一行(第 1 行):“previous definition of the valueareahere”(值area之前在这里定义过)。这是第一个area,也就是 Rust 保留的那个。^^^那一行(第 5 行):“arearedefined here”(area在这里被重新定义)。这是第二个,被拒绝的就是它。note:说area在“这个模块的值命名空间(value namespace)”里只能定义一次。每个模块就像一个抽屉,同一个抽屉里每个名字都必须是唯一的。
如果是两个同名结构体,note 里写的会是“type namespace”(类型命名空间)。Rust 把类型(结构体、枚举、trait、模块)和值(函数、常量、静态变量)放在两份不同的名单里。所以 mod shapes 和 fn shapes 可以并存,但两个 fn area 或两个 struct User 不行。
想象一个文件夹里有两个都叫 report.txt 的文件。电脑会拒绝第二个,不是因为它有什么问题,而是因为再说 report.txt,电脑就分不清你指的是哪个了。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”在 Rust 里,模块里的一个名字只指向一个项。写 area(...) 时,Rust 只靠名字找到这个函数,而不看参数的个数或类型。这样代码读起来很简单:只有一个 area 要找,而且新加一个函数,永远不会悄悄改变旧的调用用的是哪个版本。
支持重载的语言会根据参数来挑选版本,一旦涉及数字之间的类型转换或者默认参数,规则就变得很绕。Rust 选择不这样做。当你想要“同一个名字处理不同的输入”时,就用 trait 或泛型,选择哪个版本都清楚地体现在类型上。函数的更多内容见函数。
修复 1:给每个函数起自己的名字
Section titled “修复 1:给每个函数起自己的名字”大多数时候都选这个方法。接收不同输入的两个函数,做的通常是不同的事,名字可以把这一点说清楚。如果其中一个是你忘了删掉的旧副本,那就直接删掉它:
fn rectangle_area(width: f64, height: f64) -> f64 { width * height}
fn circle_area(radius: f64) -> f64 { 3.14159 * radius * radius}
fn main() { println!("{}", rectangle_area(2.0, 3.0)); println!("{}", circle_area(1.0));}输出:
63.14159标准库也是这么做的:String::new()、String::with_capacity(10) 和 String::from("hi") 是创建 String 的三种方式,用了三个不同的名字。
修复 2:放进不同的模块
Section titled “修复 2:放进不同的模块”当这个短名字在各自的场景里确实就是最好的名字时,选这个方法。比如每种图形都叫 area,或者数据库代码里有一个 User,API 代码里又有另一个 User。每个模块都有自己的一套名字,冲突就没了。调用时在前面写上模块名:
mod rectangle { pub fn area(width: f64, height: f64) -> f64 { width * height }}
mod circle { pub fn area(radius: f64) -> f64 { 3.14159 * radius * radius }}
fn main() { println!("{}", rectangle::area(2.0, 3.0)); println!("{}", circle::area(1.0));}输出:
63.14159别忘了 pub,否则在 main 里调用会得到 E0603:“function is private”(函数是私有的)。参见模块。
修复 3:用 trait 代替重载
Section titled “修复 3:用 trait 代替重载”当你想要一个调用能用于多种类型时,选这个方法,这通常也正是人们想用重载的原因。为每种输入定义一个类型,再让它们实现同一个 trait。这样每种图形都有自己的 area,Rust 会根据值的类型挑出正确的那个:
trait Shape { fn area(&self) -> f64;}
struct Rectangle { width: f64, height: f64,}
struct Circle { radius: f64,}
impl Shape for Rectangle { fn area(&self) -> f64 { self.width * self.height }}
impl Shape for Circle { fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }}
fn print_area<T: Shape>(shape: &T) { println!("面积:{}", shape.area());}
fn main() { print_area(&Rectangle { width: 2.0, height: 3.0 }); print_area(&Circle { radius: 1.0 });}输出:
面积:6面积:3.14159这比修复 1 的代码多,所以当你有好几种图形,或者好几个遵循同一模式的函数(area、perimeter、draw)时才划算。泛型函数 print_area<T: Shape> 对你以后新加的任何图形都适用。参见把 trait 用作函数参数和泛型。
如果你想要的是“同一个函数,多传或少传一个参数”,还有一个更轻巧的办法:接收一个 Option,比如 fn greet(name: &str, title: Option<&str>),调用时传 None 或 Some("Dr.")。
修复 4:用 use ... as ... 给导入改名
Section titled “修复 4:用 use ... as ... 给导入改名”当冲突发生在你自己的项和一行 use 之间时,选这个方法。严格来说,这是一个兄弟错误 E0255,第一行的说法是一样的:
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 { | ^^^^^^^^^^ `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; | +++++++++++++help: 建议用 as 给导入换一个绑定名,也就是起一个本地的别名。比起 rustc 给的占位名 OtherError,最好选一个能说明来历的名字,比如 IoError:
use std::io::Error as IoError;use std::io::ErrorKind;
#[derive(Debug)]enum Error { NotFound(String), Invalid(u32),}
fn main() { let mine = Error::NotFound(String::from("config.toml")); let other = Error::Invalid(42); let disk = IoError::new(ErrorKind::Other, "磁盘已满");
println!("{:?}", mine); println!("{:?}", other); println!("{}", disk);}输出:
NotFound("config.toml")Invalid(42)磁盘已满两行 use 互相冲突,比如 use std::fmt::Result; 和 use std::io::Result;,会得到第三个兄弟错误 E0252(`Result` reimported here,Result 在这里被重复导入)。修法一样:写成 use std::io::Result as IoResult;,或者导入模块(use std::io;)再写 io::Result。把你自己的枚举改名(AppError)也可以。
在 impl 块里重载方法。 在 impl Timer 里写两个 fn new,会得到另一个错误代码 E0592:duplicate definitions with name `new` (名为 new 的定义重复了)。原因一样,修法也一样:按它们的用途命名,比如 Timer::new() 和 Timer::with_seconds(30)。参见结构体。
重构时复制粘贴。 你复制了一个函数打算改一改,或者从别的文件粘贴了一个 struct User,却忘了删掉旧的那个。测试里也很常见:同一个 mod tests 里有两个 #[test] fn it_works(),就会得到 E0428。修复:删掉旧副本,或者给测试改个名。
重复的枚举变体。 enum Color { Red, Red } 也会报 E0428,指向第二个 Red,note 里写的是“type namespace of this enum”(这个枚举的类型命名空间)。修复:删掉或改名重复的变体。参见枚举。
- E0061:参数个数不对。你想重载函数时,它常常紧跟在 E0428 后面出现。
- E0425:正好相反的问题:你用到的名字一次都没有定义。
- E0433:像
circle::area这样的路径,第一部分(模块)找不到。 - E0034:两个 trait 给同一个类型提供了同名方法,你需要说明用的是哪一个。
- E0255(
use和本地项冲突)、E0252(两行use冲突)、E0592(impl里方法重复):上面介绍过的兄弟错误。 - 所有编译错误:错误页面的完整列表。
小测验
1. 你在一个文件里写了 fn total(a: u32) 和 fn total(a: u32, b: u32)。会发生什么?
2. mod report 和 fn report() 能放在同一个模块里吗?
3. 你的文件里有 use std::fmt::Result;,你又定义了 enum Result。干净的修法是什么?
得分: 0 / 3
- E0428 — Rust error codes index(英文):官方解释,附有两个同名结构体的示例。
- 定义模块来控制作用域与私有性 — Rust 程序设计语言:模块怎样让代码的每一部分拥有自己的一套名字。
- 使用 as 关键字提供新的名称 — Rust 程序设计语言:
fmt::Result和io::Result的例子,用as解决。 - Trait:定义共同行为 — Rust 程序设计语言:在类型之间共享行为,这就是 Rust 对重载的回答。
- Namespaces — The Rust Reference(英文):错误 note 里提到的类型命名空间和值命名空间的确切规则。