Rust Установка и инструментарий: rustup, cargo, clippy, rustfmt
0%

Установка и инструментарий: rustup, cargo, clippy, rustfmt

Установка и инструментарий: rustup, cargo, clippy, rustfmt

В обзоре трека мы говорили, зачем язык устроен так, как устроен. Теперь практика — и начать надо с честного признания: в Rust инструментарий не «вспомогательный», он часть языка.

В C установка компилятора — это apt install gcc, а дальше вы собираете всё сами: систему сборки, менеджер пакетов (которого нет), форматтер, статический анализатор, способ фиксировать версии. Полдня уходит на решения, к вашей задаче отношения не имеющие — подробно об этом в сборке и линковке. В Rust этих решений нет: cargo идёт в комплекте с версии 1.0, и спорить про систему сборки в Rust-проекте так же бессмысленно, как про синтаксис if.

Задача статьи — не «поставить компилятор» (это одна команда), а собрать в голове модель: кто кого запускает, где что лежит, почему сборка занимает столько времени и что делать, когда компилятор напечатал сорок строк текста вместо бинарника.

Что вообще значит «установить Rust»

Слово «Rust» на диске раскладывается в четыре разные сущности, и путать их — источник половины проблем новичка.

  • Toolchain (тулчейн) — конкретный набор rustc + cargo + стандартная библиотека, слепок на определённую дату. Их может быть несколько одновременно.
  • Channel (канал)stable, beta, nightly или точная версия вроде 1.85.0. Канал — правило, по которому rustup решает, какой тулчейн подтянуть.
  • Component (компонент) — опциональная часть тулчейна: clippy, rustfmt, rust-analyzer, rust-src, llvm-tools. Ставятся и удаляются независимо.
  • Target (цель) — платформа, под которую вы компилируете: x86_64-unknown-linux-gnu, wasm32-unknown-unknown, thumbv7em-none-eabihf. Для каждой цели нужна своя предсобранная std (или её отсутствие — no_std, см. unsafe и FFI).

rustup управляет тулчейнами, cargo — проектом, rustc компилирует, остальное — компоненты и подкоманды. Ставится всё одной командой, потому что rustup и есть официальный установщик.

rustup: не «менеджер версий», а диспетчер

# Linux и macOS. Скрипт интерактивный: спросит про профиль и PATH
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Неинтерактивно — для Dockerfile и провижининга
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal

# Windows: rustup-init.exe с https://rustup.rs. Обязательно нужны Visual Studio
# Build Tools с нагрузкой «Разработка классических приложений на C++»,
# иначе в системе просто не окажется линкера

Rust не приносит свой линкер и свой C-компилятор. На Linux нужен cc (build-essential), на macOS — xcode-select --install, на Windows — MSVC. Отсюда самая первая ошибка новичка: error: linker `cc` not found.

После установки на диске появляются два каталога:

~/.rustup/      # тулчейны: по каталогу на каждый, по 1.5–2 ГБ
  toolchains/stable-x86_64-unknown-linux-gnu/
  settings.toml # здесь живут directory overrides — запомните этот файл
~/.cargo/
  bin/          # shim-ы: cargo, rustc, rustfmt, clippy-driver, ...
  registry/     # скачанные .crate и распакованные исходники зависимостей
  config.toml   # глобальные настройки cargo

Ключевая деталь: ~/.cargo/bin/cargo — это не cargo. Это shim: крошечная программа, которая спрашивает у rustup «какой тулчейн активен в этом каталоге?» и передаёт управление настоящему бинарнику из ~/.rustup/toolchains/…. Понимание механизма снимает целый класс «мистики».

Цепочка разрешения тулчейна: кто решает, какой rustc запустится

rustup show                 # активный тулчейн и почему именно он
rustup which rustc          # путь к настоящему бинарю, а не к shim
rustc -vV                   # версия, host-triple, версия LLVM, хеш коммита
rustup update               # обновить все установленные каналы
rustup component add clippy rustfmt rust-src rust-analyzer
rustup target add wasm32-unknown-unknown
rustup doc --std            # документация std локально, работает офлайн

rust-src нужен rust-analyzer, чтобы вы проваливались в исходники std по Ctrl+Click, и -Z build-std — чтобы пересобрать std под экзотическую цель. Ставьте сразу.

Каналы и релизный поезд

Rust выпускает релиз каждые шесть недель, без исключений, с 2015 года. Модель — поезд: master каждую ночь становится nightly; раз в шесть недель от него отрезается beta; ещё через шесть недель beta становится stable. Между мержем фичи и её появлением в stable проходит от 6 до 12 недель.

Редакции (editions) — единственный легальный способ ломать совместимость: 2015, 2018, 2021, 2024. Редакция указывается в Cargo.toml и меняет правила языка (в 2018 переработали пути в модулях, в 2021 — захват полей замыканиями, в 2024 — семантику if let и unsafe-атрибуты). Ключевое свойство: крейты разных редакций линкуются друг с другом. Ваш проект может быть на 2024, а зависимость — на 2015. Поэтому обновление редакции не превращается в «Python 2 → 3»: cargo fix --edition делает бо́льшую часть работы механически.

Когда нужен nightly. Честно: в 2026 году stable закрывает подавляющее большинство прикладных задач. Nightly оправдан в четырёх случаях — встроенная разработка с -Z build-std, инструменты вроде cargo-expand и cargo-udeps, nightly-опции rustfmt, участие в разработке самого языка. Всё остальное — соблазн, за который вы платите: nightly ломается ровно в тот момент, когда надо выкатить хотфикс. Хак RUSTC_BOOTSTRAP=1, включающий nightly-фичи на stable, существует для сборки самого компилятора; в продакшене это способ построить систему, которая развалится на следующем обновлении без предупреждения.

# rust-toolchain.toml в корне репозитория — версия команды, зафиксированная в git
[toolchain]
channel = "1.85.0"                    # или "stable"; точная версия воспроизводимее
components = ["rustfmt", "clippy", "rust-analyzer"]
targets = ["wasm32-unknown-unknown"]
profile = "minimal"

Положили файл — и любой cargo внутри проекта подтянет ровно этот тулчейн, скачав его при первом запуске. Никаких «а у меня собирается». Это единственный слой фиксации, живущий в репозитории, — в отличие от rustup override, который прячется в домашнем каталоге и невидим коллегам.

Компромисс известен. channel = "stable" даёт свежие линты и оптимизации, но CI может внезапно покраснеть после релиза Rust: новый clippy-линт плюс -D warnings равно сломанная сборка на коммите, который её не трогал. Точная версия даёт предсказуемость ценой ручных апдейтов. Практика больших команд — точная версия плюс плановый бамп отдельным PR.

Cargo: модель сборки, а не набор команд

Сначала словарь, потому что cargo им пользуется в сообщениях об ошибках. Крейт — единица компиляции: rustc вызывается один раз на крейт, а не на файл, всё дерево модулей внутри компилируется вместе. Пакет — то, что описывает один Cargo.toml; он содержит несколько крейтов-таргетов: библиотеку (src/lib.rs), бинарники (src/main.rs, src/bin/*.rs), примеры, тесты, бенчмарки. Workspace — несколько пакетов с общим Cargo.lock и общим каталогом target/.

cargo new hello --bin      # бинарный пакет (по умолчанию), сразу делает git init
cargo new mylib --lib
    Creating binary (application) `hello` package
note: see more `Cargo.toml` keys and their definitions at
      https://doc.rust-lang.org/cargo/reference/manifest.html

Что реально происходит при cargo build

   Compiling serde v1.0.219
   Compiling tokio v1.47.1
    Building [==============>            ] 31/58: axum, sqlx-core
    Finished `release` profile [optimized] target(s) in 1m 47s

Три вывода из этой схемы объясняют почти всё поведение cargo:

  1. Единица кэширования — крейт целиком. Изменили строку в своей библиотеке — пересобирается вся библиотека и всё, что от неё зависит. Отсюда рекомендация дробить крупные крейты на членов воркспейса: структура проекта.
  2. Фичи объединяются по всему графу. Если крейт A просит у serde фичу derive, а B — нет, serde соберётся один раз с derive. Отсюда «почему у меня в бинарнике оказался chrono» и внезапные пересборки после cargo build -p one.
  3. build.rs и процедурные макросы компилируются под хост. При кросс-компиляции сборок две: одна под вашу машину, вторая целевая. Это не баг — это причина, почему кросс-сборка вдвое дольше.

Cargo.toml, Cargo.lock и профили

[package]
name = "orders"
version = "0.1.0"
edition = "2024"           # правила языка
rust-version = "1.85"      # MSRV: cargo откажется собирать на более старом rustc

[dependencies]
serde = { version = "1", features = ["derive"] }
# default-features = false отключает лишнее и заметно сокращает время сборки
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }

[dev-dependencies]         # только для тестов и бенчмарков, в релиз не попадёт
proptest = "1"

[profile.release]
overflow-checks = true     # проверки переполнения и в релизе: дёшево и спасает
debug = "line-tables-only" # читаемые бэктрейсы без раздувания бинарника

version = "1" — не «ровно 1.0.0», а caret-требование >=1.0.0, <2.0.0. Точные версии выбирает Cargo.lock. Старое правило «бинарники коммитят, библиотеки нет» устарело: актуальная рекомендация Cargo — коммитить всегда, потребители вашей библиотеки его всё равно игнорируют, а ваш собственный CI получает воспроизводимость. В CI добавляйте --locked: команда упадёт, если лок-файл пришлось бы изменить, вместо того чтобы молча обновиться.

dev (по умолчанию) release
opt-level 0 — оптимизаций нет 3
debug / debug-assertions полная инфа, проверки включены нет / выключены
overflow-checks truei32::MAX + 1 паникует false — обёртка по модулю
incremental true false
Сборка / работа быстро / очень медленно медленно / эталон

Это ловушка для новичков: «Rust медленнее Python» почти всегда означает «я замерял debug-сборку». На числовом коде разница легко достигает 50×. Любые замеры — только --release, по методике из измерения производительности. Разница в overflow-checks тоже не косметика: включать их в релизе стоит почти всегда — цена единицы процентов, выигрыш — не молчаливо неверные числа.

Полезный трюк: собирать свой код без оптимизаций, а зависимости — с ними. Они пересобираются редко, а дебажная сборка перестаёт тормозить из-за неоптимизированного serde.

[profile.dev.package."*"]   # все зависимости, кроме крейтов воркспейса
opt-level = 3

Цикл обратной связи и его цена

Главный ресурс Rust-разработчика — не память и не CPU, а время до ответа компилятора. Инструменты выстраиваются в лестницу: чем ниже ступень, тем быстрее и уже.

cargo check — самая недооценённая команда. Она делает разбор, разрешение имён, проверку типов и заимствований, но не запускает LLVM и не линкует. Именно кодогенерация и линковка съедают основное время, поэтому check обычно в 3–5 раз быстрее build. Пока вы воюете с типами и borrow checker’ом, check — ваш основной цикл.

Честно про скорость компиляции

Это главная цена Rust, и приукрашивать её не надо. Порядки величин для типичного веб-сервиса (axum + tokio + sqlx, около 50 прямых зависимостей и 400 транзитивных):

Операция Время
Холодная сборка --release 3–8 мин
Инкрементальный cargo check после правки функции 1–3 с
Инкрементальный cargo build (dev) 5–25 с
Инкрементальный cargo build --release 40 с – 3 мин
CI без кэша 5–15 мин

Для сравнения: Go собирает сопоставимый сервис за 5–20 секунд с нуля. Это не «Rust плохо написан», это плата за мономорфизацию обобщений, за отсутствие стабильной бинарной ABI, за LTO и за то, что оптимизирует не JIT в рантайме, а компилятор заранее. Разработчики компилятора работают над этим постоянно, но принципиально порядок не изменится.

Практический вывод: если задача — скрипт на двести строк, запускаемый раз в неделю, Rust избыточен. Экономия трёх миллисекунд рантайма не окупит трёх минут сборки и трёх недель обучения. Rust окупается, когда код запускается миллионы раз, живёт годами или работает там, где сбой стоит дорого. Куда именно уходит время, покажет cargo build --release --timings — HTML-отчёт с диаграммой Ганта по крейтам: видно долгострои и места, где параллелизм схлопнулся в одну ветку. Это ровно тот подход «сначала измерь», о котором рабочий процесс оптимизации.

Компилятор говорит с вами — научитесь читать

Диагностики rustc — не сообщения об ошибках, а маленькие уроки. Игнорировать их структуру означает учить Rust в разы дольше.

fn consume(s: String) -> usize {
    s.len()
}

fn main() {
    let text = String::from("привет");
    let owned = consume(text);            // владение уехало внутрь функции
    println!("{} {}", owned, text.len()); // а здесь мы им снова пользуемся
}
error[E0382]: borrow of moved value: `text`
 --> src/main.rs:8:30
  |
6 |     let text = String::from("привет");
  |         ---- move occurs because `text` has type `String`,
  |              which does not implement the `Copy` trait
7 |     let owned = consume(text);
  |                         ---- value moved here
8 |     println!("{} {}", owned, text.len());
  |                              ^^^^ value borrowed here after move
  |
note: consider changing this parameter type in function `consume` to borrow
      instead if owning the value isn't necessary
 --> src/main.rs:1:15
  |
1 | fn consume(s: String) -> usize {
  |    -------    ^^^^^^ this parameter takes ownership of the value
help: consider cloning the value if the performance cost is acceptable
  |
7 |     let owned = consume(text.clone());
  |                             ++++++++

For more information about this error, try `rustc --explain E0382`.

Анатомия диагностики rustc: где причина, а где следствие

Разбор по слоям — это и есть навык. error[E0382] — уникальный код; rustc --explain E0382 печатает страницу с правилом и минимальным примером, полный каталог — error_codes. --> src/main.rs:8:30 — место, где компилятор споткнулся; почти никогда не то, которое надо править. Вторичные span-ы (----) излагают модель: String не реализует Copy, значит передача в функцию — перемещение, а не копия; это буквально урок по владению, встроенный в ошибку. note: — соображение уровнем выше: может, consume вообще не нужно владение и хватит &str? help: с плюсиками — конкретный патч, помеченный как machine-applicable.

И тут же главная грабля. Компилятор предложил .clone() — значит, надо клонировать? Нет. help оптимизирует «чтобы собралось», а не «чтобы было хорошо». Правильная реакция на E0382 в большинстве случаев — сменить сигнатуру на fn consume(s: &str) -> usize, и тогда text вообще никуда не уезжает. Стадию «расставляю .clone() везде, где ругается» проходят все, но задерживаться в ней не надо: она даёт работающий, но бессмысленно копирующий код.

cargo fix                            # применить machine-applicable подсказки
cargo fix --edition                  # миграция на новую редакцию
cargo clippy --fix                   # то же для линтов clippy
cargo build --message-format=short   # компактный вывод, когда ошибок много

Правило чтения каскада: первая ошибка настоящая, остальные производные. Сто ошибок после правки одной строки почти всегда значат, что не собрался какой-то тип или не разрешилось имя. Правьте верхнюю, пересобирайте, смотрите снова.

rustfmt: спор о форматировании закрыт

cargo fmt                     # отформатировать весь пакет
cargo fmt --all -- --check    # ничего не менять, упасть при отличиях (для CI)

Rust решил вопрос радикально: есть один официальный стиль, он реализован в rustfmt, обсуждать отступы не принято. Экономия на код-ревью огромна — диффы содержат только смысловые изменения. Конфиг держите коротким (rustfmt.toml: max_width = 100, edition = "2024" — и обычно этого хватает).

Тонкость, о которую спотыкаются: многие интересные опции доступны только на nightlyimports_granularity, group_imports, wrap_comments. На stable они молча игнорируются. Хочет команда автосортировку импортов — придётся форматировать через cargo +nightly fmt и ставить nightly отдельным шагом CI. Это осознанный компромисс, а не поломка.

clippy: код-ревью, встроенное в сборку

Clippy — больше семисот линтов, разложенных по группам с разной строгостью:

Группа По умолчанию Что ловит
correctness deny Почти наверняка баг: x == x, сравнение f32 через ==
suspicious / style warn «Вы имели в виду не это» и неидиоматичность
complexity / perf warn Можно проще; лишние аллокации и collect()
pedantic / nursery allow Придирки и экспериментальное, бывают ложные срабатывания
restriction allow Запреты под политику: «никаких unwrap», «арифметика только проверяемая»
cargo clippy --all-targets --all-features -- -D warnings
warning: called `map(..).flatten()` on an `Iterator`
 --> src/text.rs:7:10
  |
7 |         .map(|line| line.split_whitespace())
  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `flat_map(|line| line.split_whitespace())`
  |
  = help: for further information visit
          https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten
  = note: `#[warn(clippy::map_flatten)]` on by default

Заметьте: каждый линт даёт URL со страницей, где написано, почему так лучше. Прогнать clippy по своему первому проекту и прочитать эти страницы — один из самых быстрых способов стать в языке своим.

Конфигурация — в Cargo.toml (современный способ вместо атрибутов в lib.rs):

[lints.rust]
unsafe_code = "forbid"                        # forbid нельзя перебить локальным allow

[lints.clippy]
pedantic = { level = "warn", priority = -1 }  # приоритет ниже: точечные правила побеждают
module_name_repetitions = "allow"             # шумный линт из pedantic
unwrap_used = "warn"                          # из restriction: ловим unwrap в библиотеке

Точечные исключения обязаны нести обоснование — #[allow] без комментария это красный флаг на ревью, через полгода никто не вспомнит, почему линт заглушили:

// Индекс проверен выше, границы гарантированы инвариантом парсера.
#[allow(clippy::indexing_slicing)]
let byte = buf[pos];

Цена -D warnings в CI. Если тулчейн задан как stable, новый релиз Rust может добавить линт и покрасить сборку на коммите, который её не трогал. Два рабочих решения: закрепить точную версию в rust-toolchain.toml либо разделить CI на блокирующий шаг (-D clippy::correctness) и информационный. Про устройство таких конвейеров — основы CI.

Однородность тулинга — не случайность, а политика: каждый инструмент, доказавший пользу, втягивался в официальный дистрибутив и получал гарантии совместимости (rustfmt и clippy стали компонентами rustup в 2018, rust-analyzer — в 2021, cargo add въехал в cargo в 2022). Обратная сторона — альтернатив мало, экосистема сознательно не поощряет разнообразие в этом слое. По сравнению с зоопарком CMake/Meson/Bazel/autotools в мире C++ (современные альтернативы C) это чистый выигрыш.

rust-analyzer и редакторы

rust-analyzer — LSP-сервер: автодополнение, переход к определению, инлайн-типы и, главное, диагностики настоящего компилятора прямо в буфере. Ставится компонентом rustup или расширением редактора. Три настройки стоит сделать сразу:

{
  "rust-analyzer.check.command": "clippy",
  "rust-analyzer.cargo.targetDir": "target/ra",
  "rust-analyzer.inlayHints.typeHints.enable": true
}

Вторая — про реальную боль: rust-analyzer в фоне гоняет cargo check и борется за блокировку target/ с вашим терминалом. Симптом — «Blocking waiting for file lock on build directory». Отдельный каталог сборки решает вопрос ценой лишних гигабайт. Конкретные конфигурации — в VS Code, Neovim и JetBrains (RustRover). Инлайн-подсказки типов в Rust полезнее, чем в большинстве языков: тип часто выводится из длинной цепочки итераторов, и увидеть его глазами дешевле, чем разбирать ошибку.

Расширения cargo, которые стоит поставить

cargo install cargo-nextest cargo-audit cargo-deny --locked
# либо не пересобирать полчаса, а ставить готовые бинарники:
cargo install cargo-binstall && cargo binstall cargo-nextest cargo-audit
Инструмент Зачем
cargo-nextest Ранер тестов: процесс на тест, читаемый вывод. Doc-тесты не запускает (тестирование)
cargo-audit / cargo-deny База уязвимостей RustSec; лицензии, дубликаты версий, запрещённые крейты
cargo tree (встроен) -d показывает дубликаты, -i <крейт> — кто его тянет
cargo-expand Развернуть макросы и увидеть, что нагенерил derive
cargo-machete Неиспользуемые зависимости, работает на stable
cargo-bloat / cargo-msrv Что занимает место в бинарнике; минимальная поддерживаемая версия Rust
cargo-sweep Чистка старых артефактов в target/, когда кончился диск
bacon Фоновый check/clippy/test с живым выводом — основной цикл разработки

Про cargo install в CI: всегда --locked, иначе инструмент соберётся с новыми версиями зависимостей и может не собраться вовсе. И помните, что бинарь ложится в ~/.cargo/bin — он глобальный, а не привязан к проекту.

Ускорение сборки: что реально помогает

# .cargo/config.toml в корне проекта
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]   # линковка в 2–10 раз быстрее GNU ld

[alias]
c = "check --all-targets"
lint = "clippy --all-targets --all-features -- -D warnings"

По убыванию отдачи:

  1. Быстрый линкер. На инкрементальных debug-сборках линковка часто больше половины времени. mold на Linux, lld кроссплатформенно. В свежих stable-версиях rust-lld стал линкером по умолчанию для x86_64-unknown-linux-gnu — проверьте changelog своей версии, возможно, настраивать уже нечего.
  2. cargo check вместо cargo build в цикле правок: бесплатно, экономит 60–80% времени.
  3. Дробление на крейты воркспейса. Единица пересборки — крейт; монолит на 40 тысяч строк пересобирается весь.
  4. Меньше зависимостей и default-features = false. cargo tree -d покажет дубликаты версий — их сборка оплачивается дважды.
  5. debug = "line-tables-only" вместо полной отладочной информации: бэктрейсы читаемы, объём падает в разы.
  6. sccache (RUSTC_WRAPPER=sccache) — кэш результатов компиляции, общий между проектами; требует отключить инкрементальность, выигрывает в основном на CI и при переключении веток.
  7. В CI — Swatinem/rust-cache, в Docker — cargo-chef: кэшировать слой зависимостей отдельно от слоя вашего кода. Это разница между 12-минутной и 90-секундной сборкой образа (контейнеры).

Чего делать не надо: включать lto = "fat" и codegen-units = 1 в dev-профиле. Это инструменты финальной релизной сборки, они множат время сборки на два-три ради процентов рантайма. Отдельно про Windows: антивирус, сканирующий target/, замедляет сборку в разы — исключение каталога из проверки самая дешёвая оптимизация на этой платформе.

Targets и кросс-компиляция

Тройка цели читается как <архитектура>-<вендор>-<ОС>-<ABI>:

x86_64-unknown-linux-gnu     обычный сервер Linux, динамическая glibc
x86_64-unknown-linux-musl    статическая линковка, бинарь без зависимости от libc
aarch64-apple-darwin         Apple Silicon
wasm32-unknown-unknown       WebAssembly без ОС
thumbv7em-none-eabihf        Cortex-M4F: none = операционной системы нет вообще

Платформы разбиты на уровни: Tier 1 — гарантированно собирается и проходит тесты, Tier 2 — собирается, Tier 3 — «в теории существует». Список — в Platform Support.

rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

И тут же грабля, на которой спотыкаются все: rustup target add даёт стандартную библиотеку под цель, но не даёт линкер и системные библиотеки этой цели. Если проект тянет что-то через C (OpenSSL, SQLite, zlib), сборка упадёт на линковке. Решения: cross — обёртка, собирающая в готовом Docker-образе с полным кросс-тулчейном; cargo-zigbuild — использует Zig как кросс-компилятор C, часто проще; либо избегать C-зависимостей вовсе. Последнее — общая рекомендация: default-features = false, features = ["rustls-tls"] у reqwest убирает зависимость от системного OpenSSL и заодно чинит половину проблем с Docker-сборками. Про встроенные цели подробно — в треке встраиваемых систем.

CI-конвейер: минимальный, но достаточный

name: ci
on: [push, pull_request]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with: { components: "rustfmt, clippy" }
      - uses: Swatinem/rust-cache@v2       # кэш ~/.cargo и target/
      - run: cargo fmt --all -- --check    # быстрее всего — пусть падает первым
      - run: cargo clippy --all-targets --all-features -- -D warnings
      - run: cargo test --all-features --locked
      - run: cargo doc --no-deps           # заодно ловит битые ссылки в документации

Порядок шагов не случаен: сначала то, что падает за секунды, потом дорогое. --locked обязателен — без него CI может тихо подтянуть другие версии, и «зелёный» прогон перестанет что-либо доказывать. Аудит зависимостей отдельным джобом (rustsec/audit-check) — тема, разобранная в безопасности цепочки поставок.

Типичные грабли

  • apt install rustc cargo. Дистрибутивные пакеты отстают на месяцы, ставятся в /usr/bin и не подчиняются rustup. Симптом — cargo видит одну версию, rustc другую. Ставьте только через rustup.
  • PATH не подхватился. После установки нужен новый шелл или source ~/.cargo/env. Классика: «rustup поставился, а cargo: command not found».
  • Забытый rustup override. Сделан год назад в одном каталоге, живёт в ~/.rustup/settings.toml, невидим коллегам. rustup override list покажет все, rustup override unset --nonexistent подчистит мёртвые.
  • Cargo.lock в .gitignore. Воспроизводимость исчезает, «вчера работало» становится нормой.
  • .clone() как ответ на любую ошибку заимствования. Собирается, но модель не выучена, а копий стало больше. Читайте note:, а не только help:.
  • Замеры на debug-сборке. Не значат ничего. Всегда --release.
  • target/ на десятки гигабайт. Норма для Rust: полсотни проектов легко съедают 100+ ГБ. cargo clean, cargo sweep --time 30.
  • RUSTFLAGS переопределяет, а не дополняет rustflags из конфига, и любое их изменение инвалидирует весь кэш сборки: разные значения в терминале и в редакторе дают бесконечные пересборки.
  • [build] rustflags и [target.*] rustflags не складываются — если задан таргет-специфичный блок, глобальный игнорируется. Один из самых неочевидных пунктов Cargo Book.
  • nightly ради одной удобной фичи. Через полгода обнаружится, что обновиться нельзя, потому что фича уехала.
  • cargo update «на всякий случай» перед релизом. Обновляет весь граф разом; обновляйте адресно: cargo update -p serde.

Чек-лист готовой среды

  • rustup show показывает нужный тулчейн, rustup which rustc ведёт в ~/.rustup.
  • Установлены компоненты clippy, rustfmt, rust-src, rust-analyzer.
  • Есть системный C-компилятор и линкер; на Linux настроен mold или lld.
  • В репозитории лежат rust-toolchain.toml, Cargo.lock, rustfmt.toml, секция [lints].
  • Редактор подключён к rust-analyzer, check.command = "clippy", отдельный targetDir.
  • cargo check, cargo clippy, cargo fmt --check, cargo test проходят локально.
  • CI гоняет fmt → clippy → test с --locked и кэшем; аудит зависимостей включён.
  • Замеры производительности делаются только на --release.

Мини-итог

Среда Rust — это три слоя, и каждый решает свою задачу. rustup отвечает на вопрос «какой компилятор запустится», и ответ должен лежать в git, а не в чьём-то домашнем каталоге. cargo отвечает за граф зависимостей, единицы компиляции и профили — почти всё «странное» поведение сборки объясняется тремя фактами: единица кэша это крейт, фичи объединяются глобально, макросы собираются под хост. clippy и rustfmt — код-ревью, вынесенное в машину: они закрывают споры о стиле и учат идиоматике быстрее любой книги.

Но самый важный инструмент — тот, который вы не устанавливаете. Компилятор Rust разговаривает с вами полными предложениями: показывает, откуда взялось значение, где оно перестало вам принадлежать и что с этим делать. Дальше мы разберём владение и времена жизни как модель памяти, но выучите вы их не по тексту статьи, а по сорока диагностикам E0382, E0499 и E0597, которые прочитаете внимательно, а не пролистаете до help:.

И последнее, про честность. Минуты сборки — реальная цена, которую вы платите каждый день. Недели на освоение borrow checker’а — реальная цена, которую вы платите один раз. Обе окупаются там, где код работает долго, часто и близко к железу; обе не окупаются в разовом скрипте или в прототипе, который выкинут через неделю. Выбор инструмента — это выбор того, за что платить, и Rust здесь не исключение.

Источники

Что дальше

Основы Rust: типы, переменные, управление потоком, функции — система типов, которая делает возможным всё остальное: почему целые типы имеют явную ширину, зачем неизменяемость по умолчанию, чем match отличается от switch и почему в Rust почти нет операторов, зато почти всё является выражением.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов