Раздел 23 · Rust

Зачем Rust. rustup, cargo и первая программа

junior~40 мин

открытый урокЭтот раздел читается без входа. Войди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Зачем Rust. rustup, cargo и первая программа

> Привет! Мы открываем раздел про Rust. Этот язык умеет быть быстрым как C, но при этом безопасным. В этом уроке мы вместе установим всё нужное, напишем первую программу и разберём главные фишки Rust. Не переживай: мы будем двигаться постепенно, шаг за шагом, и всё станет понятно.

Идея

Давай начнём с простого вопроса: зачем учить ещё один язык? Ты уже знаком с TypeScript, возможно работал с Go, Haskell или Gleam. Чем же Rust отличается?

Представь, что до 2015 года у программистов был непростой выбор:

  • Либо работать как в C: управлять памятью вручную. Это даёт максимальную скорость, но легко допустить ошибку , и программа может «упасть» или стать уязвимой.
  • Либо использовать языки со сборщиком мусора (JS, Go, Java): здесь безопаснее, но программа тратит дополнительные ресурсы, а паузы для очистки памяти могут появиться в самый неподходящий момент.

Rust предлагает третий, более умный путь. Давай разберём, в чём его сила!

Обещание первое: безопасность памяти без сборщика мусора

Представь, что в C программист , как водитель без правил: можно ехать куда угодно и как угодно. Это даёт полную свободу, но и высокий риск аварии. Из‑за таких «аварий» в памяти (например, когда программа пытается использовать уже освобождённую память) возникает около 70 % серьёзных уязвимостей , так выяснили Microsoft и команда Chromium.

Языки со сборщиком мусора вроде JS или Go действуют осторожнее: они сами следят за порядком в памяти. Но за эту безопасность приходится платить , программа работает медленнее, тратит больше ресурсов, а «уборка» памяти может начаться в самый неподходящий момент.

Rust решает эту проблему по‑умному. Вместо того чтобы перекладывать заботу о памяти на программиста (как C) или на рантайм (как JS), он доверяет это компилятору. Система владения , это как умный помощник: он заранее (ещё на этапе компиляции) понимает, когда память можно освободить, и делает это автоматически. При этом в готовой программе нет ни сборщика мусора, ни лишних проверок , только быстрый и безопасный код, как в C.

Обещание второе: нулевые абстракции

Второе обещание звучит как маркетинг, но проверяется дизассемблером: высокоуровневый код компилируется в то же, что ты написал бы руками. Цепочка из filter и map по массиву разворачивается в тот же машинный цикл, что и for со счётчиком, без замыканий, без аллокаций, без виртуальных вызовов. Принцип называется zero-cost abstractions: ты не платишь за то, чем не пользуешься, а тем, чем пользуешься, не смог бы воспользоваться дешевле и вручную.

Для тебя это значит, что весь багаж из ФП-серии раздела 02 здесь применим без оговорок про производительность: итераторы, алгебраические типы, паттерн-матчинг в Rust не сахар поверх медленного рантайма, а конструкции, которые компилятор честно стирает в ноль. В блоке про биты и машину мы откроем дизассемблер и убедимся в этом глазами.

Обещание третье: честный компилятор

Третье обещание самое непривычное. Компилятор Rust отказывается собирать программу, в которой видит возможность ошибки: использование перемещённого значения, висячую ссылку, гонку данных между потоками. Да, гонка данных в Rust это ошибка компиляции, а не баг, который всплывает под нагрузкой раз в неделю.

Из этого следует стиль работы, знакомый тебе по Haskell: подружился с компилятором, значит программа во многом корректна по построению. Рефакторинг перестаёт быть прыжком веры: поменял тип, компилятор перечислил все места, которые перестали сходиться, починил по списку. Цена тоже знакомая: компилятор спорит, и первые недели спорит много. Весь этот раздел построен так, чтобы спор превратился в диалог.

Где это всё живёт

Rust , уже не просто «язык для энтузиастов», а серьёзный инструмент в руках профессионалов. Его используют:

  • в ядре Linux (второй по популярности язык после C);
  • в драйверах Windows и компонентах Android;
  • в браузере Firefox;
  • в инфраструктуре Cloudflare и AWS;
  • в блокчейнах Solana и Polkadot;
  • в популярных инструментах вроде ripgrep.

В нашем курсе мы тоже не будем ограничиваться простыми примерами. Впереди тебя ждут интересные проекты:

  • эмулятор учебного процессора;
  • эмулятор NES;
  • сетевой игровой движок;
  • блокчейн‑узел и мини‑EVM в разделе Web3.

Всё это возможно благодаря главной силе Rust , он сочетает скорость C с надёжностью, которой нет даже у языков со сборщиком мусора.

Демонстрация

Отлично, теперь перейдём к практике , установим всё необходимое. Хорошая новость: как и в Haskell, для Rust есть удобный единый установщик , всё ставится одной командой!

rustup: твой помощник по установке Rust

Представь, что rustup , это универсальный мастер для Rust. Он:

  • установит компилятор и все нужные компоненты;
  • поможет переключаться между версиями;
  • будет вовремя обновлять инструменты.

По сути, rustup для Rust , то же самое, что nvm для Node или GHCup для Haskell.

Давай установим Rust:

  1. Открой rustup.rs.
  2. Запусти установщик командой:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  1. Соглашайся с настройками по умолчанию , они подойдут для учёбы и работы. Установщик поставит стабильную версию Rust (stable) и добавит нужные пути в настройки твоей оболочки.
  2. После завершения установки перезапусти терминал.
  3. Проверь, что всё установилось правильно:
rustc --version
# rustc 1.96.0 (или новее)
cargo --version
# cargo 1.96.0

Не переживай из‑за точного номера версии , главное, чтобы всё работало. Мы будем использовать канал stable , это надёжная версия, которая обновляется каждые шесть недель и гарантирует совместимость.

Обновлять Rust в будущем будет так же просто , достаточно выполнить команду rustup update.

Что у нас появилось после установки:

ИнструментЗачем он нужен
rustcкомпилятор , превращает код из .rs файлов в готовую программу
cargoглавный помощник: создаёт проекты, собирает код, запускает программы, управляет зависимостями
clippyумный ассистент , подсказывает, как сделать код лучше и избежать распространённых ошибок
rustfmtформаттер , автоматически приводит код к единому стилю, принятому в сообществе Rust
rust-analyzerпомощник для редактора , показывает подсказки, типы переменных, помогает с автодополнением и рефакторингом

Не обязательно использовать rustc напрямую , cargo возьмёт на себя большинство задач. А вот clippy и rustfmt стоит начать использовать с самого начала: они помогут писать код в едином стиле и избегать распространённых ошибок.

Настраиваем редактор

Чтобы было удобнее писать код, установи расширение rust‑analyzer для своего редактора:

  • Для VS Code , найди расширение rust‑analyzer в магазине расширений.
  • Для JetBrains , используй плагин RustRover.

Как проверить, что всё работает:

  1. Открой любой файл с расширением .rs.
  2. Наведи курсор на переменную , через секунду появится подсказка с её типом.

Это очень полезная функция: Rust умеет выводить типы автоматически, и не все они явно указаны в коде. Подсказки помогут лучше понимать, что происходит в программе.

Пишем первую программу

Теперь создадим наш первый проект с помощью cargo:

cargo new hello_rust
cd hello_rust

После выполнения команд ты увидишь простую структуру проекта:

hello_rust
├── Cargo.toml
└── src
    └── main.rs

Разберём, что есть в проекте:

  • Cargo.toml , это «паспорт» проекта, похожий на package.json в JavaScript. Здесь указывается имя, версия и зависимости проекта.
  • src/main.rs , основной файл с кодом.

В Cargo.toml ты увидишь что‑то вроде этого:

[package]
name = "hello_rust"
version = "0.1.0"
edition = "2024"

[dependencies]

Обрати внимание на поле edition , оно указывает версию Rust. Раз в три года выходит новая редакция языка с небольшими улучшениями синтаксиса. При этом компилятор умеет работать с кодом разных версий , так что старый код не сломается.

В файле src/main.rs уже есть простая программа:

fn main() {
    println!("Hello, world!");
}

Запустим её:

cargo run
#    Compiling hello_rust v0.1.0
#     Finished `dev` profile
#      Running `target/debug/hello_rust`
# Hello, world!

Что произошло:

  1. cargo собрал программу в папке target/debug/.
  2. Запустил получившийся исполняемый файл.
  3. На экране появилось приветствие.

Заметь: получившаяся программа , самодостаточная. Ты можешь скопировать её на другой компьютер (даже если там нет Rust) , она всё равно будет работать!

Один синтаксический звоночек на будущее: println! заканчивается восклицательным знаком, потому что это не функция, а макрос. Пока запомни простое правило: видишь ! после имени, значит эта штука разворачивается в код на этапе компиляции и потому умеет то, что функции не позволено, например принимать разное число аргументов. Серьёзный разговор про макросы ждёт в отдельном блоке.

Цикл разработки: полезные команды

Не всегда нужно сразу собирать готовую программу , часто достаточно просто проверить код. Для этого есть удобная команда:

cargo check

cargo check проверяет код на ошибки с помощью компилятора, но не создаёт исполняемый файл. Благодаря этому команда работает в разы быстрее, чем cargo build.

Хорошая привычка: после написания небольшого фрагмента кода сразу запускай cargo check. Так ты быстро обнаружишь ошибки и исправишь их. В Rust компилятор , твой друг и помощник, а не строгий контролёр!

Вот основные команды, которые ты будешь использовать:

КомандаЧто делает
cargo checkбыстро проверяет код на ошибки, не создавая исполняемый файл
cargo runсобирает программу и сразу запускает её
cargo buildсобирает debug‑версию программы (для разработки) в папке target/debug
cargo build --releaseсобирает оптимизированную версию программы (для релиза) в папке target/release
cargo clippyпроверяет код на соответствие лучшим практикам и подсказывает, как его улучшить
cargo fmtавтоматически форматирует код по общепринятым правилам

Важный момент про --release: debug‑версия программы не оптимизирована и может работать в десятки раз медленнее. Все обещания о высокой скорости относятся именно к оптимизированной версии (--release). Поэтому если хочешь измерить реальную производительность, используй cargo build --release.

Разбираемся с владением: первый опыт

Теперь давай познакомимся с одной из ключевых особенностей Rust , системой владения. Подробный разбор будет в уроке 03, но уже сейчас полезно увидеть, как это работает на практике.

Попробуй добавить в main.rs такой код:

fn main() {
    let s = String::from("привет");
    let t = s;
    println!("{s}");
}

По логике многих языков программирования, после выполнения let t = s переменные t и s должны указывать на одну и ту же строку. И программа должна напечатать «привет».

Но если запустить cargo check, мы увидим ошибку:

error[E0382]: borrow of moved value: `s`
 --> src/main.rs:4:15
  |
2 |     let s = String::from("привет");
  |         - move occurs because `s` has type `String`,
  |           which does not implement the `Copy` trait
3 |     let t = s;
  |             - value moved here
4 |     println!("{s}");
  |               ^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let t = s.clone();
  |              ++++++++

Это работает borrow checker , специальный механизм компилятора Rust. Давай разберём сообщение об ошибке по шагам , так ты научишься понимать, что именно пошло не так:

  1. Заголовок ошибки (error[E0382]) говорит о проблеме: ты пытаешься использовать значение после того, как оно было перемещено.
  2. Стрелочки и пояснения показывают всю историю:
    • в строке 2 создаётся значение, его владелец , s;
    • в строке 3 владение передаётся переменной t (value moved here);
    • в строке 4 программа пытается использовать s, но s больше ничего не владеет.
  3. Совет от компилятора (help) предлагает готовое решение: если тебе действительно нужны две копии строки, используй .clone().

Что здесь произошло? В Rust у каждого значения может быть только один владелец. Когда ты пишешь let t = s, ты не копируешь строку и не создаёшь вторую ссылку на неё , ты передаёшь владение. Теперь строкой владеет t, а переменная s становится «недействительной». Компилятор знает это и не позволит тебе использовать s.

Зачем такая строгость? Это как раз и обеспечивает безопасность памяти без сборщика мусора:

  • понятно, кто отвечает за освобождение памяти;
  • исключено двойное освобождение памяти;
  • существенно снижена вероятность утечек памяти по сравнению с языками без системы владения. Тем не менее, утечки всё ещё возможны, например, при использовании mem::forget или создании циклических ссылок через Rc.

Всё это проверяется ещё на этапе компиляции , до запуска программы.

Мы подробно разберём владение, перемещение (move) и освобождение памяти (drop) в уроке 03. А пока запомни простую аналогию: каждое значение в Rust , это предмет, который может находиться только у одного хозяина. Передал его кому‑то , сам больше им не владеешь.

И заметь, какого качества сама ошибка: история значения по шагам, точные места в коде, рабочее исправление. Сообщения rustc считаются эталоном в индустрии, и у любого кода ошибки есть развёрнутое объяснение:

rustc --explain E0382

Как учить Rust с удовольствием

У Rust иногда говорят, что он сложный. Но на самом деле большая часть трудностей возникает из‑за привычек, которые мы приносим из других языков. Давай договоримся сразу , так будет проще и приятнее:

Читай ошибки целиком, не спеши гуглить. В других языках ошибка часто означает «всё сломалось», и хочется сразу найти решение в поиске. В Rust всё иначе: сообщение об ошибке , это мини‑урок. Оно покажет, где проблема, объяснит, почему она возникла, и даже предложит, как исправить. Если читать ошибки внимательно, учить язык будет в разы легче.

Не торопись использовать unsafe. В Rust есть ключевое слово unsafe, которое позволяет обойти некоторые гарантии безопасности (например, при работе с сырыми указателями). При этом базовые проверки компилятора, включая проверку заимствований (borrow checking), остаются активными. Но оно нужно только для особых задач (например, при работе с железом). На первых порах оно тебе не понадобится. Если кажется, что без unsafe не обойтись , скорее всего, можно решить задачу иначе. Мы разберём unsafe позже, когда придёт время.

Используй unwrap с осторожностью. В Rust функции, которые могут завершиться с ошибкой, возвращают Result. Метод .unwrap() говорит: «Достань значение, а если ошибка , заверши программу». В учебных примерах он допустим, но не стоит злоупотреблять им. Лучше учиться обрабатывать ошибки правильно , этому посвящён отдельный урок.

Доверяй компилятору. Бывает, что код выглядит правильным, но компилятор его не принимает. Вместо того чтобы искать обходные пути, попробуй подумать: а правильно ли спроектированы данные? Чаще всего ошибка компилятора , это подсказка, что в логике есть неясность, которую другой язык мог бы просто проигнорировать.

И ещё один хороший совет: запускай cargo clippy после каждой решённой задачи. Этот инструмент знает сотни лучших практик и поможет писать код так, как это делают опытные разработчики. По сути, это бесплатный помощник‑ревьюер, который доступен тебе с первого дня.

Практика

Слов про язык было много, пора написать на нём первую функцию. Задача нарочно крошечная: один format! и тело-выражение, зато тесты настоящие.

ДЗ

Дальше

В уроке 02 мы начнём говорить на Rust по‑серьёзному: разберём скаляры и составные типы, изменяемость и shadowing, if как выражение и четыре вида циклов. После этого урока ты сможешь уверенно читать и писать простой Rust‑код, не спотыкаясь о синтаксис.