Ошибка Rust E0252: the name is defined multiple times (два импорта)
E0252 значит: две строки use приносят одно и то же имя. Хрестоматийный случай — use std::fmt::Result; вместе с use std::io::Result;: два разных типа, и оба называются Result. Бывает ещё fmt::Error и io::Error или два трейта Write. Ошибка очень частая и исправляется легко — обычно достаточно поменять одну-две строки 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; // ❌ a second `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; // ❌ a second `Result` | ++++++++++++++По-русски: «имя Result определено несколько раз». Первый импорт отмечен как «предыдущий», второй — как «импортирован повторно», а help предлагает переименовать второй через as.
Вот программа, которая её вызывает (комментарий // ❌ оставлен на английском, потому что rustc цитирует эту строку; он значит «второй Result»):
use std::fmt::Result;use std::io::Result; // ❌ a second `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("not a number")), }}
fn main() { match parse_temperature("21") { Ok(t) => println!("{}", t), Err(e) => println!("error: {}", e), }}После неё rustc выводит ещё четыре ошибки: E0107 («type alias takes 0 generic arguments»), две E0308 о несовпадении типов и одну E0277. Не гоняйся за ними. Rust оставил первый импорт, поэтому каждый Result в файле теперь значит fmt::Result, и parse_temperature теряет смысл. Исправь E0252 — и все они исчезнут.
Что это значит
Заголовок раздела «Что это значит»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 так делает
Заголовок раздела «Почему Rust так делает»Внутри одного модуля каждое имя указывает ровно на один элемент. Строка use ничего не копирует, она добавляет короткое имя для элемента, который живёт в другом месте. Две строки use, добавляющие одно и то же короткое имя, сделали бы неоднозначным каждый следующий Result, поэтому Rust останавливается на импорте, а не гадает при каждом использовании.
У E0252 две «сестры» с тем же заголовком: E0255 — строка use против твоего собственного элемента, и E0428 — два твоих собственных элемента. За всеми тремя стоит одно правило: в модуле одно имя — одна вещь. Подробнее — в уроке Модули.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: Импортируй модули и пиши fmt:: и io::
Заголовок раздела «Исправление 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ошибка: это не числоБонус: обычный Result<T, E> из прелюдии (prelude) по-прежнему доступен без всякого импорта.
Исправление 2: Переименуй один импорт через as
Заголовок раздела «Исправление 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Простой Result, который втайне значит fmt::Result, может удивить следующего читателя кода, поэтому исправление 1 обычно понятнее. as хорош для длинных имён, например use std::collections::BTreeMap as Map;.
Исправление 3: Импортируй трейт через as _
Заголовок раздела «Исправление 3: Импортируй трейт через as _»Выбирай этот вариант для трейтов, которые ты импортируешь только ради их методов. Классика — два трейта Write: fmt::Write позволяет писать через write! в String, а io::Write — в stdout или в файл. Если импортировать оба по имени, они конфликтуют:
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; // ❌ a second `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; // ❌ a second `Write` | +++++++++++++Это та же ошибка, только для трейта. Программа с ошибкой:
use std::fmt::Write;use std::io::Write; // ❌ a second `Write`
fn main() { let mut report = String::new(); write!(report, "{} tests passed", 12).unwrap();
let mut out = std::io::stdout(); writeln!(out, "{}", report).unwrap();}Здесь ты нигде не пишешь Write сам, тебе нужны только его методы. as _ импортирует трейт без имени, поэтому конфликтовать нечему, а оба набора методов продолжают работать:
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. Подробнее — в уроке Трейты.
Исправление 4: Убери повторный импорт того же элемента
Заголовок раздела «Исправление 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` again | ^^^^^^^-- | | | `HashMap` reimported here | help: remove unnecessary importПрограмма с ошибкой (комментарий значит «снова HashMap»):
use std::collections::HashMap;use std::collections::{HashMap, HashSet}; // ❌ `HashMap` again
fn main() { let mut stock: HashMap<&str, u32> = HashMap::new(); stock.insert("apples", 3);
let mut sold_out: HashSet<&str> = HashSet::new(); sold_out.insert("pears");
println!("apples: {}, sold out: {}", stock["apples"], sold_out.len());}Обрати внимание, что подсказка другая: никакого 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Подробнее — в уроках HashMap и HashSet.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»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.
Два крейта с одинаковым именем типа. use chrono::Duration; и use std::time::Duration; — частая пара в проектах, которые работают с датами. Исправление: оставь короткое имя для одного, а у другого пиши модуль, например chrono::Duration. Подробнее — в уроке Дата и время.
Импорты со звёздочкой. use std::fmt::*; вместе с use std::io::*; не дают E0252 на строках use. Проблема появится позже, на первом Result, в виде ошибки без кода: error: `Result` is ambiguous («Result неоднозначен»). Исправление: импортируй нужные имена явно, как в исправлении 1.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0255: строка
useконфликтует с твоей собственной структурой, перечислением или функцией с тем же именем. - E0428: два твоих собственных элемента с одним именем, часто из-за попытки перегрузить функцию.
- E0432: неразрешённый импорт, когда строка
useуказывает на то, чего не существует. - E0034: два трейта в области видимости дают типу методы с одинаковым именем.
- E0308: несовпадение типов, одна из ошибок-следствий после конфликта
Result. - Все ошибки компилятора: полный список страниц об ошибках.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. В одном файле нужны fmt::Result и io::Result. Какие импорты обычно выбирают как чистое решение?
2. Тебе нужны методы и std::fmt::Write, и std::io::Write, но имена трейтов ты сам не пишешь. Что сработает?
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 (на английском): почему трейты и типы делят пространство имён типов из заметки к ошибке.