Установка и инструментарий: 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/…. Понимание механизма снимает целый класс «мистики».
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:
- Единица кэширования — крейт целиком. Изменили строку в своей библиотеке — пересобирается вся библиотека и всё, что от неё зависит. Отсюда рекомендация дробить крупные крейты на членов воркспейса: структура проекта.
- Фичи объединяются по всему графу. Если крейт
Aпросит уserdeфичуderive, аB— нет,serdeсоберётся один раз сderive. Отсюда «почему у меня в бинарнике оказался chrono» и внезапные пересборки послеcargo build -p one. 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 |
true — i32::MAX + 1 паникует |
false — обёртка по модулю |
incremental |
true |
false |
| Сборка / работа | быстро / очень медленно | медленно / эталон |
Это ловушка для новичков: «Rust медленнее Python» почти всегда означает «я замерял debug-сборку». На числовом коде разница легко достигает 50×. Любые замеры — только --release, по методике из измерения производительности. Разница в overflow-checks тоже не косметика: включать их в релизе стоит почти всегда — цена единицы процентов, выигрыш — не молчаливо неверные числа.
Полезный трюк: собирать свой код без оптимизаций, а зависимости — с ними. Они пересобираются редко, а дебажная сборка перестаёт тормозить из-за неоптимизированного serde.
[profile.dev.package."*"] # все зависимости, кроме крейтов воркспейса
opt-level = 3
Цикл обратной связи и его цена
Главный ресурс Rust-разработчика — не память и не CPU, а время до ответа компилятора. Инструменты выстраиваются в лестницу: чем ниже ступень, тем быстрее и уже.
прямо в буфере, 0.1–2 с"] B --> C{"Собирается?"} C -->|нет| A C -->|да| D["cargo check
анализ без кодогенерации, 1–5 с"] D --> E{"Чисто?"} E -->|нет| A E -->|да| F["cargo clippy
кодогенерации нет, но линтов 700+, 3–15 с"] F --> G{"Чисто?"} G -->|нет| A G -->|да| H["cargo test
сборка и прогон, 10 с – 5 мин"] H --> I{"Зелено?"} I -->|нет| A I -->|да| J["cargo build --release
LLVM и линковка, 1–15 мин"] J --> K["Артефакт"] style D fill:#7fae5b22,stroke:#7fae5b style F fill:#5b8dd622,stroke:#5b8dd6 style J fill:#d1873f22,stroke:#d1873f
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`.
Разбор по слоям — это и есть навык. 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" — и обычно этого хватает).
Тонкость, о которую спотыкаются: многие интересные опции доступны только на nightly — imports_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"
По убыванию отдачи:
- Быстрый линкер. На инкрементальных debug-сборках линковка часто больше половины времени.
moldна Linux,lldкроссплатформенно. В свежих stable-версияхrust-lldстал линкером по умолчанию дляx86_64-unknown-linux-gnu— проверьте changelog своей версии, возможно, настраивать уже нечего. cargo checkвместоcargo buildв цикле правок: бесплатно, экономит 60–80% времени.- Дробление на крейты воркспейса. Единица пересборки — крейт; монолит на 40 тысяч строк пересобирается весь.
- Меньше зависимостей и
default-features = false.cargo tree -dпокажет дубликаты версий — их сборка оплачивается дважды. debug = "line-tables-only"вместо полной отладочной информации: бэктрейсы читаемы, объём падает в разы.sccache(RUSTC_WRAPPER=sccache) — кэш результатов компиляции, общий между проектами; требует отключить инкрементальность, выигрывает в основном на CI и при переключении веток.- В 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 здесь не исключение.
Источники
- The Rust Programming Language — официальная книга, глава 1 про установку
- The rustup Book — каналы, компоненты, разрешение оверрайдов
- The Cargo Book — манифест, профили, конфигурация, воркспейсы
- Rust Error Codes Index — все коды ошибок с объяснениями
- Clippy Lints — поиск по всем линтам с обоснованиями
- rustfmt configuration — все опции и их статус на stable
- Platform Support — уровни поддержки платформ
- The Rust Performance Book, гл. Compile Times, Nicholas Nethercote
- rust-analyzer manual и Nightly Rust про релизный поезд
- cargo-nextest, cargo-deny, cargo-binstall
Что дальше
Основы Rust: типы, переменные, управление потоком, функции — система типов, которая делает возможным всё остальное: почему целые типы имеют явную ширину, зачем неизменяемость по умолчанию, чем match отличается от switch и почему в Rust почти нет операторов, зато почти всё является выражением.