Зачем Rust и первая программа
Ирма Окунева · наставница Гильдии кузнецов
Добро пожаловать в Окалину. Город держится на Гильдии, а Гильдия держится на одном правиле, оно высечено над воротами: «У каждой вещи один хозяин». Ты пришёл учеником, значит, начнёшь с горна. Сначала форма, потом удар. Сегодня форма: что за язык ты берёшь в руки и почему он так придирчив.
Окалина это чешуя оксида на горячем железе, первая ржавчина на металле. Отсюда и имя языка: Rust. В этом курсе ты пройдёшь путь от ученика Гильдии до мастера, а каждый блок уроков будет изделием, которое ты соберёшь этап за этапом. Первое изделие называется forge: мастерская с горном, складом и молотом. Сегодня ты растопишь её горн.
Мы открываем раздел про Rust. Этот язык умеет быть быстрым как C, но при этом безопасным. В этом уроке разберём, откуда у Rust эта репутация, напишем первую программу прямо на странице и прочитаем первую записку от компилятора.
Идея
Начнём с вопроса: зачем учить ещё один язык? Ты уже знаком с TypeScript, возможно работал с Go, Haskell или Gleam. Чем же Rust отличается?
Представь, что до 2015 года у программистов был непростой выбор:
- Либо работать как в C: управлять памятью вручную. Это даёт максимальную скорость, но легко допустить ошибку, и программа может «упасть» или стать уязвимой.
- Либо использовать языки со сборщиком мусора (JS, Go, Java): здесь безопаснее, но программа тратит дополнительные ресурсы, а паузы для очистки памяти могут появиться в самый неподходящий момент.
Rust предлагает третий путь, и у него три обещания.
Обещание первое: безопасность памяти без сборщика мусора
Программист на C похож на водителя без правил: можно ехать куда угодно и как угодно. Это полная свобода, но и высокий риск аварии. По данным Microsoft и команды Chromium, около 70 % серьёзных уязвимостей возникают из‑за таких «аварий» в памяти (например, когда программа пытается использовать уже освобождённую память).
Языки со сборщиком мусора вроде 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 с надёжностью, которой нет даже у языков со сборщиком мусора.
Первая программа прямо здесь
Ставить ничего не нужно: редактор задач на этой странице собирает и запускает Rust на сервере школы. Первые уроки ты пройдёшь в нём, а свой компьютер настроим тогда, когда проект перерастёт одну страницу.
Самая короткая программа на Rust выглядит так:
fn main() {
println!("Hello, world!");
}
Здесь всего две вещи. fn main() это функция, с которой начинается любая программа: её вызывает сам запуск. println! печатает строку и ставит в конце перевод строки.
Один синтаксический звоночек на будущее: println! заканчивается восклицательным знаком, потому что это не функция, а макрос. Пока запомни простое правило: видишь ! после имени, значит эта штука разворачивается в код на этапе компиляции и потому умеет то, что функции не позволено, например принимать разное число аргументов. Серьёзный разговор про макросы ждёт в отдельном блоке.
Забудешь восклицательный знак, и компилятор откажется собирать программу. Это первая ошибка, которую видит почти каждый, кто начинает Rust:
Записка Ферапонта ·
E0423«expected function, found macro
println». Ты позвалprintln, как зовут функцию, а это макрос. Пишется с восклицательным знаком:println!("..."). Я не придираюсь, я просто не выдаю то, чего нет на полке.
Компилятор Rust пишет длинно, но по делу: что не так, в какой строке и часто как исправить. Записки Ферапонта в этом курсе стоят ровно там, где ты встретишь ошибку впервые: читай их как перевод с компиляторного на человеческий.
Хочешь поставить Rust сейчас? Одна команда в терминале:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh, затемcargo new hello_rust,cd hello_rustиcargo run. Подробно про установку, редактор,cargo check,cargo clippyи разницу debug и--releaseпоговорим в уроке про Cargo. Пока всё работает и без этого.
Разбираемся с владением: первый опыт
Теперь познакомимся с одной из ключевых особенностей Rust, системой владения. Подробный разбор будет в уроке 04, но уже сейчас полезно увидеть, как это работает на практике.
Попробуй собрать такой код:
fn main() {
let s = String::from("привет");
let t = s;
println!("{s}");
}
По логике многих языков программирования, после выполнения let t = s переменные t и s должны указывать на одну и ту же строку. И программа должна напечатать «привет».
Но компилятор такой код не пропустит:
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();
| ++++++++
Записка Ферапонта ·
E0382«borrow of moved value:
s». Строку ты отдалtв третьей строке, а в четвёртой опять просишь её уs. Так не бывает: вещь одна, хозяин один. Нужны две строки? Скажиs.clone(), и я выдам вторую под отдельную запись.
Это работает borrow checker, часть компилятора Rust. Разберём сообщение об ошибке по шагам, чтобы ты научился видеть, что пошло не так:
- Заголовок ошибки (
error[E0382]) говорит о проблеме: ты пытаешься использовать значение после того, как оно было перемещено. - Стрелочки и пояснения показывают всю историю:
- в строке 2 создаётся значение, его владелец
s; - в строке 3 владение передаётся переменной
t(value moved here); - в строке 4 программа пытается использовать
s, ноsбольше ничего не владеет.
- в строке 2 создаётся значение, его владелец
- Совет от компилятора (
help) предлагает готовое решение: если тебе нужны две копии строки, используй.clone().
Что здесь произошло? В Rust у каждого значения может быть только один владелец. Когда ты пишешь let t = s, ты не копируешь строку и не создаёшь вторую ссылку на неё: ты передаёшь владение. Теперь строкой владеет t, а переменная s становится «недействительной». Компилятор знает это и не позволит тебе использовать s.
Зачем такая строгость? Она и даёт безопасность памяти без сборщика мусора:
- понятно, кто отвечает за освобождение памяти;
- исключено двойное освобождение памяти;
- существенно снижена вероятность утечек памяти по сравнению с языками без системы владения. Тем не менее, утечки всё ещё возможны, например, при использовании
mem::forgetили создании циклических ссылок черезRc.
Всё это проверяется ещё на этапе компиляции, до запуска программы.
Мы подробно разберём владение, перемещение (move) и освобождение памяти (drop) в уроке 04. А пока запомни простую аналогию: каждое значение в Rust это предмет, который может находиться только у одного хозяина. Передал его кому‑то, и сам больше им не владеешь.
И заметь, какого качества сама ошибка: история значения по шагам, точные места в коде, рабочее исправление. Сообщения rustc считаются эталоном в индустрии, и у любого кода ошибки есть развёрнутое объяснение:
rustc --explain E0382
Как учить Rust с удовольствием
Про Rust говорят, что он сложный. Но большая часть трудностей возникает из‑за привычек, которые мы приносим из других языков. Договоримся сразу о четырёх правилах:
Читай ошибки целиком, не спеши гуглить. В других языках ошибка часто означает «всё сломалось», и хочется сразу найти решение в поиске. В Rust всё иначе: сообщение об ошибке это мини‑урок. Оно покажет, где проблема, объяснит, почему она возникла, и даже предложит, как исправить. Если читать ошибки внимательно, учить язык будет в разы легче.
Не торопись использовать unsafe. В Rust есть ключевое слово unsafe, которое позволяет обойти некоторые гарантии безопасности (например, при работе с сырыми указателями). При этом базовые проверки компилятора, включая проверку заимствований (borrow checking), остаются активными. Но оно нужно только для особых задач (например, при работе с железом). На первых порах оно тебе не понадобится. Если кажется, что без unsafe не обойтись, скорее всего, задачу можно решить иначе. Мы разберём unsafe позже, когда придёт время.
Используй unwrap с осторожностью. В Rust функции, которые могут завершиться с ошибкой, возвращают Result. Метод .unwrap() говорит: «Достань значение, а если ошибка, заверши программу». В учебных примерах он допустим, но не злоупотребляй им. Лучше учиться обрабатывать ошибки правильно: этому посвящён отдельный урок.
Доверяй компилятору. Бывает, что код выглядит правильным, но компилятор его не принимает. Вместо того чтобы искать обходные пути, попробуй подумать: а правильно ли спроектированы данные? Чаще всего ошибка компилятора это подсказка, что в логике есть неясность, которую другой язык мог бы просто проигнорировать.
И ещё один хороший совет на то время, когда поставишь Rust локально: запускай cargo clippy после каждой решённой задачи. Clippy знает сотни правил хорошего тона и подскажет, как ту же мысль пишут опытные разработчики. Это бесплатный ревьюер, который с тобой с первого дня.
Практика
Слов про язык было много, пора написать на нём первую функцию. Задача нарочно крошечная: один format! и тело-выражение, зато тесты настоящие.
Мастерская, этап 1
С этого урока у курса есть сквозное изделие: мастерская forge. Код каждого этапа переходит в следующий, а тесты этапа проверяют и все этапы до него, так что ничего из сделанного не потеряется. Первый этап короткий: растопить горн и поздороваться с Гильдией. Стартовый код нарочно не собирается, и первая записка Ферапонта ждёт тебя после кнопки «Проверить».
ДЗ
Дальше
Горн растоплен, Гильдия с тобой поздоровалась. Но в ту же неделю на складе находят два слитка с одним клеймом, и Ферапонт хмурится: по Книге выдачи так быть не может. Чтобы разобраться, мастерской придётся считать уголь и помнить состояние горна. В уроке 02 мы начнём говорить на Rust по‑серьёзному: разберём скаляры и составные типы, изменяемость и shadowing и if как выражение, а в уроке 03 четыре вида циклов и match.