Перейти к содержимому

Ошибка 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 { // ❌ same name as the import on line 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 определено несколько раз». Ниже rustc предлагает переименовать импорт через as и предупреждает, что импорт не используется.

Вот программа, которая её вызывает (комментарий // ❌ оставлен на английском, потому что rustc цитирует эту строку; он значит «то же имя, что у импорта в строке 1»):

use std::io::Error;
enum Error { // ❌ same name as the import on line 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!("name is fine"),
Err(Error::EmptyName) => println!("name is empty"),
Err(Error::TooLong(n)) => println!("name has {} letters, max is 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). Типы, трейты и модули делят один список имён, а функции, константы и статические переменные — другой. Поэтому use std::mem::swap; вместе с твоей fn swap — та же ошибка, только в «пространстве имён значений» (value namespace).

Представь класс, где учатся два Саши. Учитель уже не может просто позвать «Саша!». Кому-то нужно прозвище, например «Саша Б.», — и именно его as даёт импорту.

Имя в модуле указывает ровно на один элемент. Когда Rust читает Error::EmptyName в строке 10, ему нужно знать, какой Error ты имеешь в виду, и угадывать он отказывается. Если бы он тихо выбрал твоё перечисление, строка use ничего бы не делала; если бы выбрал io::Error, до твоего перечисления нельзя было бы добраться. В любом случае молчаливый выбор спрятал бы ошибку.

Поэтому E0255 — родственница E0428 (два твоих собственных элемента с одним именем) и E0252 (две строки use с одним именем). Правило одно: в модуле одно имя — одна вещь. Как работает use, рассказано в уроке Модули.

Выбирай этот вариант, когда в одном файле нужны оба типа — а так бывает часто: твой Error для правил приложения и io::Error для файлов и сети. Дай импорту прозвище, по которому видно, откуда он. IoError читается лучше, чем OtherError из подсказки rustc:

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!("ошибка ввода-вывода: {}", e),
}
}

Вывод:

Ok(())
Err(EmptyName)
Err(TooLong(15))
ошибка ввода-вывода: это имя зарезервировано
▶ Попробуй сам

Имена вроде "Ferris the Crab" оставлены на английском, потому что программа считает их длину через .len(), а кириллическая буква занимает 2 байта.

as меняет имя только в этом файле. Везде остальном тип по-прежнему называется std::io::Error.

Выбирай этот вариант, если хочешь, чтобы при каждом использовании Error было видно, откуда он. Импортируй родительский модуль через use std::io; и пиши io::Error. Твой собственный Error сохраняет короткое имя. Многие программисты на Rust предпочитают такой стиль для типов с частыми именами вроде Error и Result:

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!("ошибка ввода-вывода: {}", e);
}
}

Вывод:

имя пустое
ошибка ввода-вывода: зарезервированное имя
▶ Попробуй сам

Выбирай этот вариант, когда твой тип будет использоваться во многих файлах или другими людьми. Конкретное имя вроде 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 заметно сокращает такие перечисления ошибок. Подробнее — в уроке Свои типы ошибок.

Исправление 4: Удали импорт, который не используется

Заголовок раздела «Исправление 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),
}
}

Вывод:

имя пустое
▶ Попробуй сам

Твоя функция с именем из std. use std::mem::swap; вместе с fn swap(a: &mut i32, b: &mut i32) даёт E0255 с текстом «previous import of the value swap here» и заметкой про «value namespace». Исправление: назови свою функцию swap_values или убери импорт, если хотел написать свою.

Копирование между модулями. Ты переносишь struct User в mod models, добавляешь сверху use models::User;, но забываешь удалить старую struct User в main.rs. Исправление: удали старую копию, чтобы User означал только тот, что в models.

Трейт с именем из std. use std::fmt::Display; и твой собственный trait Display конфликтуют так же («previous import of the trait Display»). Исправление: переименуй свой трейт (Describe, Render) или пиши use std::fmt; и fmt::Display.

Полезно знать: импорт со звёздочкой (glob), например use std::io::*;, никогда не вызывает 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