Rust часто описывают через безопасность памяти и borrow checker, но повседневная работа начинается с другого: создать проект, разложить код по модулям, подключить зависимость и запустить тесты. Именно эти навыки нужны в первый день, а не знание тонкостей лайфтаймов. Ниже — практический маршрут, который можно повторить в терминале за вечер.
Установка инструментов
Официальный способ — rustup. Он ставит компилятор, cargo и стандартную документацию, а также позволяет переключать версии. Не смешивайте пакет из репозитория дистрибутива с rustup: разные пути к бинарникам приводят к путанице.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustc --version
cargo --version
Если политика компании запрещает скачивать установщик, возьмите пакет rustc и cargo из репозитория, но проверьте версию: старый компилятор не соберёт проект с новой редакцией. Для учебных задач хватит и его, для рабочих — лучше rustup.
Создание проекта
Cargo создаёт каталог с манифестом, точкой входа и git-репозиторием. Флаг —bin означает исполняемую программу, —lib — библиотеку. Для первого знакомства достаточно бинарного проекта.
cargo new notes --bin
cd notes
cargo run
cargo tree
Сборка кладёт результат в target/debug. Каталог не нужно добавлять в git: cargo new уже создал .gitignore. Для проверки синтаксиса без запуска есть cargo check — он быстрее полной сборки и экономит время при частых правках.
Модули и видимость
Модуль — это пространство имён и граница видимости. Файл src/store.rs становится модулем store, если объявить его в src/main.rs через mod store. Всё, что должно быть доступно снаружи, помечается pub. По умолчанию элементы приватны, и это защищает от случайных связей.
mod store;
use store::Store;
fn main() {
let mut s = Store::new();
s.add("купить хлеб");
println!("заметок: {}", s.len());
}
Сам модуль хранит структуру и методы. Обратите внимание на &str в аргументе: функция принимает строковый срез и не забирает владение, поэтому вызывающий код сохраняет свою строку.
#[derive(Debug)]
pub struct Note {
pub text: String,
}
pub struct Store {
notes: Vec<Note>,
}
impl Store {
pub fn new() -> Self {
Self { notes: Vec::new() }
}
pub fn add(&mut self, text: &str) {
self.notes.push(Note { text: text.to_string() });
}
pub fn len(&self) -> usize {
self.notes.len()
}
}
impl Default for Store {
fn default() -> Self {
Self::new()
}
}
Когда файлов становится много, сгруппируйте их в каталог с store/mod.rs и подмодулями. Главное — не держать всю программу в main.rs: через пару недель его невозможно читать.
Зависимости
Сторонние крейты подключаются в Cargo.toml. Команда cargo add подбирает актуальную версию и сама правит манифест. Фича derive у serde включает генерацию кода для сериализации; без неё придётся писать реализации вручную.
cargo add serde --features derive
cargo add serde_json
cargo build
Файл Cargo.lock фиксирует точные версии и коммитится для приложений. Для библиотек его обычно не включают. Не указывайте звёздочку в версии зависимости: сборка станет невоспроизводимой, а обновление — сюрпризом.
[package]
name = "notes"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Редакция 2024 поддерживается актуальными компиляторами и включает изменения в захвате переменных и обработке unsafe. Если проект старый, менять edition без причины не стоит: возможны предупреждения и правки.
Тесты
Модульные тесты живут рядом с кодом в блоке #[cfg(test)]. Атрибут означает, что код компилируется только при тестировании. Макрос assert_eq! сравнивает значения и печатает разницу при ошибке.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_increases_len() {
let mut s = Store::new();
s.add("тест");
assert_eq!(s.len(), 1);
}
#[test]
fn add_keeps_text() {
let mut s = Store::new();
s.add("молоко");
assert_eq!(s.notes[0].text, "молоко");
}
}
Запуск cargo test собирает тестовый профиль и выполняет все функции с атрибутом test. Фильтр по имени помогает прогнать один сценарий: cargo test add_. Флаг — —nocapture показывает вывод println!, который по умолчанию скрыт.
cargo test
cargo test add_ -- --nocapture
cargo test --release
Интеграционные тесты кладут в каталог tests/ — каждый файл там компилируется как отдельный крейт и использует публичный API. Документационные примеры в комментариях /// тоже проверяются, если запустить cargo test —doc.
Обработка ошибок без паники
В прикладном коде вместо unwrap лучше возвращать Result и использовать оператор ?. Он пробрасывает ошибку вызывающему, сохраняя контекст. Паника уместна только там, где нарушен инвариант программы.
use std::fs;
fn count_lines(path: &str) -> std::io::Result<usize> {
let text = fs::read_to_string(path)?;
Ok(text.lines().count())
}
Для своих типов ошибок удобно завести enum и реализовать Display, либо взять thiserror. Для приложений подходит anyhow: он добавляет контекст и упрощает main, возвращающий Result. Главное — не глотать ошибку пустым match.
Форматирование и линтер
rustfmt приводит код к единому стилю, clippy ищет подозрительные конструкции. Оба инструмента ставятся через rustup component. В команде clippy флаг -D warnings превращает предупреждения в ошибку сборки — удобно для CI.
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo build --release
Release-сборка включает оптимизации и занимает больше времени. Бинарник оказывается в target/release. Для распространения полезно проверить его через ldd и убедиться, что нет неожиданных динамических зависимостей.
Полезно заглянуть в cargo doc —open: локальная документация собирается по вашим крейтам и зависимостям, без интернета. Если проект обрастает функциями, включайте их через feature flags в Cargo.toml и проверяйте сборку с —no-default-features.
Рабочий цикл
- Пишите маленькими шагами и запускайте cargo check после каждой правки.
- Сначала тест, потом реализация — особенно для разбора форматов и граничных случаев.
- Держите модули по смыслу, а не по типу: store, cli, config.
- Не добавляйте зависимость ради одной функции — стандартная библиотека часто достаточна.
- Перед коммитом прогоняйте fmt, clippy и test.
Итог
Cargo снимает рутину сборки и управления зависимостями, модули помогают держать код читаемым, а встроенные тесты делают проверку частью обычного цикла. Освойте четыре команды — cargo run, cargo test, cargo fmt, cargo clippy — и вы сможете уверенно вести небольшой проект. Остальное приходит с практикой: borrow checker перестаёт мешать, когда код разбит на маленькие функции с ясными границами ответственности.