extern crate и use в Rust: в чём подвох и как не наступать на грабли
Если вы когда-нибудь писали на Rust хотя бы пару строк, то наверняка сталкивались с конструкциями extern crate и use. На первый взгляд кажется, что они делают одно и то же — подключают внешний код. Но дьявол, как всегда, кроется в деталях. Этот вопрос входит в топ самых популярных на Stack Overflow (тысячи просмотров!), и новички путаются в нём постоянно. Давайте раз и навсегда разберёмся, чем отличается extern crate от use, где какая магия происходит и как избежать типичных ошибок.
Коротко о главном: что есть что
Представьте, что ваш проект на Rust — это большой дом. extern crate — это доставка стройматериалов на участок. Вы говорите компилятору: «Эй, вот этот ящик с кирпичами (крейт) я хочу использовать, привези его». Без этой команды компилятор даже не будет знать о существовании внешней библиотеки.
use — это уже внутренняя навигация по дому. Вы говорите: «Я хочу, чтобы дверь из этой комнаты (модуля) вела прямо на кухню (конкретный тип или функцию)». use создаёт алиасы (короткие пути) к уже известным модулям и типам, чтобы не писать каждый раз полный путь.
Проще говоря: extern crate отвечает на вопрос «что мы вообще можем использовать?», а use — на вопрос «как нам удобнее к этому обращаться?».
Исторический контекст: почему все путаются
Путаница возникла не на пустом месте. В старых версиях Rust (до 2018 edition) extern crate был обязателен для любого внешнего крейта. Выглядело это так:
// Rust 2015 — старый стиль
extern crate serde;
use serde::{Serialize, Deserialize};
Начиная с Rust 2018 edition, компилятор научился сам находить зависимости из Cargo.toml. Теперь писать extern crate для большинства крейтов не нужно. Достаточно просто добавить зависимость в Cargo.toml и сразу использовать use:
// Rust 2018+ — новый стиль (extern crate не обязателен)
use serde::{Serialize, Deserialize};
Вот тут-то и начинается ад для новичков. Они видят старые примеры, где extern crate используется, новые, где его нет, и пытаются понять, когда он всё-таки нужен. Спойлер: нужен, но в очень редких случаях.
Когда extern crate всё ещё нужен (и это важно!)
Есть три ситуации, где без extern crate не обойтись:
- Связывание с системными библиотеками через
#[link]— если вы пишете биндинги к C/C++ коду и используете атрибут#[link(name = "sqlite3")], тоextern crateобязателен. - Глобальные макросы — некоторые крейты (например,
log) экспортируют макросы, которые должны быть видны во всей программе. В Rust 2018 для этого нужно писатьextern crate log;. - Сборка с нестандартными именами крейтов — если вы используете
extern crate my_crate as foo;, чтобы переименовать крейт.
Во всех остальных случаях extern crate — это артефакт прошлого. Но многие разработчики по привычке продолжают его писать, а новички копируют код из старых туториалов.
Топ-5 ошибок при работе с extern crate и use
Ошибка №1: Использование extern crate там, где он не нужен
Самая частая проблема. Вы добавляете крейт в Cargo.toml, потом пишете extern crate rand; — и код работает. Но это лишняя строчка. В современных проектах (Rust 2018+) она только засоряет код и может сбить с толку других разработчиков.
// Плохо (избыточно в Rust 2018+)
extern crate rand;
use rand::Rng;
// Хорошо (достаточно)
use rand::Rng;
Ошибка №2: Попытка использовать use без extern crate для системных библиотек
Обратная ситуация. Вы пишете:
use std::os::raw::c_int; // Это работает, потому что std — встроенный крейт
Но если вы работаете с внешней C-библиотекой через #[link], то без extern crate ничего не заработает:
// Не скомпилируется без extern crate
use my_sys_bindings::some_function; // Ошибка: unresolved import
// Правильно:
extern crate my_sys_bindings;
use my_sys_bindings::some_function;
Ошибка №3: Путаница с путями модулей
Многие думают, что use может импортировать что угодно откуда угодно. На самом деле use работает только с тем, что уже «видит» компилятор. Вы не можете сделать use some_crate::foo;, если крейт some_crate не объявлен в Cargo.toml или через extern crate.
// Cargo.toml
// [dependencies]
// serde = "1.0"
// main.rs — работает
use serde::Serialize;
// main.rs — НЕ работает, если нет extern crate или записи в Cargo.toml
use non_existent_crate::Foo; // Ошибка: unresolved import
Ошибка №4: Забытый атрибут crate_type
Если вы пишете библиотеку (lib.rs), а не исполняемый файл, Rust автоматически делает все публичные элементы доступными. Но если вы пишете биндинги или используете extern crate для системных библиотек, иногда нужно указать #![crate_type = "lib"]. Без этого компилятор может не понять, как линковать крейт.
Ошибка №5: Использование extern crate внутри модуля
extern crate — это глобальная директива. Её место в корне файла (main.rs или lib.rs). Если вы напишете её внутри вложенного модуля, компилятор выдаст предупреждение или ошибку:
// main.rs
mod my_module {
extern crate serde; // Ошибка! extern crate должен быть на уровне крейта
}
Правильно выносить extern crate на верхний уровень, а use — уже внутри модулей по необходимости.
Практический пример: от старого кода к новому