Avatar ·

В чем разница между use и extern crate в Rust? Когда что использовать?

Привет, коллеги! Я недавно начал изучать Rust и запутался в двух ключевых конструкциях: `use` и `extern crate`. В документации написано, что они обе связаны с импортом, но я не понимаю, когда какую применять. Вот мой код: ```rust // Это работает extern crate rand; use rand::Rng; fn main() { let secret_number = rand::thread_rng().gen_range(1..101); println!("Секретное число: {}", secret_number); } ``` Но если я пишу так, без `extern crate`: ```rust use rand::Rng; fn main() { let secret_number = rand::thread_rng().gen_range(1..101); println!("Секретное число: {}", secret_number); } ``` Компилятор ругается: `can't find crate for rand`. Зачем тогда нужен `use`, если без `extern crate` ничего не работает? И наоборот — если я напишу только `extern crate rand`, то функция `gen_range` не будет доступна без `use`. Еще я видел, что в некоторых проектах (например, на Rust 2018) люди вообще не пишут `extern crate`, а просто используют `use`. Почему так? Это какая-то магия или я что-то упускаю? Объясните, пожалуйста, простыми словами, в чем разница и когда что писать. И да, я использую Cargo.toml для добавления зависимостей, так что `rand` там прописан. Заранее спасибо! <<>>

Краткий ответ: extern crate подключает крейт, use импортирует из него имена

Вы столкнулись с классической путаницей новичков. Давайте разберем по порядку. В Rust система импорта состоит из двух уровней:

  1. Подключение крейта — сделать внешнюю библиотеку доступной для вашего проекта.
  2. Импорт имен — вытащить конкретные типы, функции или трейты из подключенного крейта в текущую область видимости.

extern crate отвечает за первый уровень. Он говорит компилятору: "Эй, найди крейт с таким именем среди зависимостей (из Cargo.toml) и сделай его доступным как модуль верхнего уровня". Без этой команды Rust просто не знает, что такое rand в коде.

use отвечает за второй уровень. Он создает короткие алиасы для путей к элементам внутри уже подключенного крейта. Например:

extern crate rand;  // Подключаем крейт

// Теперь rand::Rng — это валидный путь, но писать его каждый раз длинно
use rand::Rng;      // Импортируем трейт Rng в текущую область видимости

fn main() {
    let mut rng = rand::thread_rng();  // rand::thread_rng — это функция из крейта
    let n: u32 = rng.gen();            // gen() — метод трейта Rng
}

Почему в современных проектах extern crate не пишут?

Начиная с Rust 2018 edition, extern crate стал опциональным для большинства случаев. Если крейт указан в Cargo.toml, компилятор автоматически подключает его как модуль верхнего уровня. То есть современный эквивалент предыдущего примера:

// Cargo.toml: rand = "0.8"
// В коде extern crate rand; не нужен!

use rand::Rng;  // Работает напрямую

fn main() {
    let mut rng = rand::thread_rng();
    let n: u32 = rng.gen();
}

Однако есть случаи, когда extern crate все еще обязателен:

  • Макиросы: если крейт экспортирует макросы (#[macro_use] extern crate serde;)
  • Крейты без модульной системы: некоторые старые крейты требуют явного подключения
  • Переименование крейта: extern crate foo as bar;
  • Пользовательские атрибуты: extern crate proc_macro;

Правило большого пальца

Если вы используете Rust 2018+ (а это стандарт с 2020 года):

  1. Добавьте зависимость в Cargo.toml
  2. Используйте use для импорта нужных имен
  3. Забудьте про extern crate, пока не столкнетесь с одним из исключений выше

Ваш второй пример не работал, потому что вы, вероятно, использовали старый edition (2015) или не указали edition = "2018" в Cargo.toml. Проверьте это!

// Cargo.toml
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"  // или 2018

[dependencies]
rand = "0.8"

После этого use rand::Rng; будет работать без extern crate.

<<>>

Дополнение: use для локальных модулей и иерархия импорта

Важно понимать, что use работает не только с внешними крейтами, но и с вашими собственными модулями. Это универсальный инструмент для навигации по модульной системе Rust.

// src/main.rs
mod utils;  // объявляем модуль

// Без use:
fn main() {
    utils::helper::do_something();  // полный путь
}

// С use:
use utils::helper;
fn main() {
    helper::do_something();  // короче
}

А extern crate — это исключительно про внешние зависимости. Его можно представить как "регистрацию" внешнего пакета в вашем проекте. После регистрации вы можете обращаться к нему через :: (как к модулю) и использовать use для сокращения путей.

Еще один нюанс: use может импортировать не только модули, но и отдельные функции, типы и трейты:

use std::collections::HashMap;  // тип
use std::fs::File;              // тип
use std::io::Read;              // трейт
use rand::random;               // функция

fn main() {
    let mut map = HashMap::new();
    map.insert("key", random::<u32>());
}

Если вы когда-нибудь увидите extern crate в современном коде (Rust 2018+), скорее всего это либо:

  • Наследие (код не обновляли)
  • Специфический случай с макросами
  • Плагин компилятора (например, extern crate proc_macro;)

В обычной разработке достаточно Cargo.toml + use.