Если бы я хотел стать разработчиком на Rust в 2025, с чего бы я начал?
Вместе с экспертами Solvery разбираемся, что нужно учить, чтобы стать прогером на Rust сейчас.
915 открытий5К показов

Rust — язык общего назначения, который ориентирован на высокую производительность и безопасное управление памятью. На нем можно писать софт практически для любого направления: CLI-утилиты, высоконагруженные сервера, десктопные приложения, мобильные приложения (с некоторыми оговорками), игры и игровые движки, прошивки для микроконтроллеров, операционные системы, драйвера и даже браузерные приложения (через компиляцию в WebAssembly).
В рейтинге языков программирования TIOBE Rust занимает 14 место. Для сравнения — в прошлом марте он был на 17 позиции. Вместе с экспертами Solvery Василием Кузенковым, full-stack разработчиком в Web3 стартапе, и Дмитрием Беляевым, Rust developer в Wildberries, разбираемся, как стать разрабом на Расте в 2025 году.

Василий Кузенков
full-stack разработчик в Web3 стартапе, ментор Solvery

Дмитрий Беляев
Rust developer в Wildberries, ментор Solvery
Немного об особенностях
Если вы до этого программировали на ООП языках, то вам, возможно, бросалось в глаза отсутствие привычных классов. Вместо них здесь алгебраические типы данных и трейты для решения expression problem. Их механизм куда больше похож на typeclasses из Haskell, что также будет для вас новой концепцией при построении крупных приложений, и нужно будет перестраивать мышление.
Также когда стартуешь, немного непривычно работать с move-семантикой, RAII, borrow checker’ом и лайф-таймами. Но компилятор сыпет довольно подробными ошибками, которые можно легко поправить, если разобраться.
А еще у Rust очень строгая типизация и очень мощная система типов, а сами типы построены так, чтобы предоставлять некоторые гарантии программисту. Например, ссылки в Rust всегда ссылаются на объект, гарантировано существующий в памяти, а стандартные строки содержат только валидный UTF-8. При этом в подавляющем большинстве случаев тип необязательно указывать явно, компилятор способен выводить типы, анализируя контекст функции целиком. Кроме того, Rust следует идеологии «компилируется, значит, работает». От логических ошибок, конечно, Раст не спасёт, но тем не менее очень большой пласт багов можно отловить на этапе кодинга. Да, это сложно, но лучше помучиться при разработке, чем потом разбираться почему упал прод.
Ещё одна отличительная фишка — абстракции с нулевой стоимостью. Rust позволяет писать высокоуровневый и понятный код, который при этом будет иметь ту же производительность, что и более низкоуровневый оптимизированный вручную вариант.
Хороший пример здесь — итерация по различным коллекциям. Многие языки позволяют использовать итераторы с их абстракциями вроде map или filter. Только такой код, как правило, будет в несколько раз медленнее, чем если то же самое переписать на циклы. Компилятор Rust способен развернуть такой итератор в обычные циклы сам, и производительность будет сравнима, а порой даже лучше, так как программисты часто при написании низкоуровневого кода заставляют процессор делать много лишних вычислений.
Сложно ли переходить на Rust
У Rust достаточно нетривиальная кривая входа, и без понимания некоторых важных принципов сложно написать код, который хотя бы будет компилироваться. Это относится не только к полным новичкам в программировании, но и к людям, которые уже владеют другим языком.
На рынке фактически все вакансии требуют уже какого-то опыта в разработке. И сложность перехода сильно разнится от вашего текущего стека. Для Go-программиста переход будет средне-сложным, а язык, возможно, покажется перегруженным. Для С++ — менее сложным, но язык покажется местами ограничивающим. Я переходил на него с JS/TS’а и столкнулся со множеством низкоуровневых концепций, о которых раньше мог не задумываться. Но если у вас есть опыт в системном программировании — переходить будет в разы проще.
Однако Rust прививает программисту очень много хороших привычек, которые меняют подход к написанию кода и на других языках. Это однозначно хороший выбор в качестве первого языка, но при условии, что у вас есть достаточно времени и терпения на освоение.
В моей практике менторства много успешных кейсов перехода на Rust с самых разных языков, но проще всего он даётся тем, кто раньше писал на современном C++ и уже понимает такие концепции, как move-семантика и RAII. Много привычного здесь обнаружат и те, кто писал на функциональных языках (Haskell или OCaml). Но в целом, для остальных тоже нет никаких проблем, даже если вы совсем новичок.
С чего начать изучать Rust
Вместе со стартом в изучении языка с The Rust Book — бесплатной официальной книгой по Rust’у — стоит углубить свои знания в более низкоуровневых вещах: в чем разница стека и кучи, что такое разметка памяти и адресация в памяти, как работает процессор, подходы и проблемы многопоточного программирования, плюс почитать про операционные системы и сети. Если вы решили осознанно применять Rust, оно вам пригодится.
Начать можно даже имея только самую базу в программировании: переменные, ветвления, циклы, функции. Крайне желательно разобраться в устройстве памяти, что такое стек и куча, а так же какие области памяти бывают помимо них.
Вот примерный список того, что нужно учить на старте:
- Переменные. Они по умолчанию неизменяемые (
let x)
. Чтобы x стал изменяемым, нужно указать это явно через let mut. Константы (const
) и статические переменные (static
) вам будут нужны редко, они имеют свои особенности. - Типы. Стоит разобраться с составными типами, такими как массивы и кортежи, а также с пользовательскими объявляемыми конструкциями
struct
(тип-произведение) иenum
(тип-сумма). Также типы данных в Расте есть стандартные: bool, i32, u64 и прочие числовые, но строки могут удивить, так как их видов сильно больше. - Match. Нужно понять такую вещь, как
pattern-matching
, познакомиться с оператором match, а также осознать, чтоpattern-matching
применяется не только в нём, а везде, где возможно объявление переменных.
Таким образом, вы можете сразу проверить что-то и присвоить результат в переменную:
Здесь мы сразу проверили и присвоили:
Тут нам потребовалась дополнительная изменяемая переменная.
Что такое Cargo
Cargo — это консольная утилита, которая устанавливается вместе с компилятором языка. Она служит одновременно для управления зависимостями, сборки проекта и запуска тестов. Плюс для Cargo есть расширения, например, в поставке по умолчанию уже есть форматтер и линтер clippy
.
Cargo рассчитан на то, что вы будете запускать его через терминал, самые полезные команды это:
cargo new
— создаёт новый шаблонный проект в указанной папке;cargo build
— собирает проект;cargo run
— собирает проект и запускает получившийся исполняемый файл;cargo check
— dry-run сборки, делает все проверки компилятора, но ничего не собирает, что заметно быстрее полноценной сборки;cargo test
— собирает проект со всеми тестами и запускает их;cargo fmt
— форматирует проект в общепринятый стиль кода;cargo clippy
— запускает линтер, очень полезно, можно подсказать более оптимальные варианты кода, найти некоторые потенциальные логические ошибки;cargo install
— устанавливает пакет, содержащий исполняемые файлы;cargo clean
— очищает все артефакты сборки.
Cargo позволяет описать структуру, настройки (профили сборки, описание крейта) и зависимости вашего крейта или даже монорепозитория (с помощью workspace). Кроме непосредственного запуска через терминал, многие вещи могут запускаться через средства интеграции в IDE, такие как rust-analyzer для VSCode.
Как работать с ownership, borrowing и lifetimes?
Для многих новичков системы владения (ownership), заимствования (borrowing) и времен жизни (lifetimes) выливаются в борьбу с компилятором раста. Эти механизмы нужны в первую очередь для безопасности памяти без сборщика мусора. Общее правило владения такое:
Каждое значение в Rust имеет переменную, которая называется его владельцем. В каждый момент времени может быть только один владелец. Когда владелец выходит из области видимости, значение уничтожается.
Когда значение перемещается (передается другой переменной или функции), владение переходит, и исходная переменная становится недействительной.
Для типов, реализующих трейт Copy (например, целые числа, булевы значения), значения копируются автоматически:
Для более сложных типов нужно использовать метод clone(), чтобы создать глубокую копию. В местах программы, где производительность не так важна — использование clone() не возбраняется.
Если же у вас критичный к производительности кусок кода, то вам также понадобится заимствоватние и лайфтаймы.
Заимствование позволяет использовать значение без получения владения через ссылки (& и &mut). Для мутабельных ссылок `&mut` есть дополнительные ограничения: в каждый момент времени может существовать только одна изменяемая ссылка на значение.
Нельзя иметь изменяемую ссылку, если уже есть неизменяемая ссылка на то же значение.
Времена жизни же гарантируют, что ссылки действительны на протяжении всего времени их использования.
Аннотация 'a указывает, что возвращаемая ссылка будет жить как минимум столько же, сколько кратчайшая из входных ссылок.
Общие советы по заимствованию здесь такие:
- Используйте ссылки, когда не нужно владение.
- Возвращайте значения из функций для передачи владения обратно.
- Используйте клонирование для создания новых экземпляров (с пониманием стоимости).
- Используйте типы с трейтом Copy, когда это возможно.
- Еще полезно не забывать о контейнерах. Rc<T> позволяет иметь несколько владельцев одного значения через счетчик ссылок, а RefCell<T> обеспечивает проверку правил заимствования в рантайме.
О структурах, перечислениях, модулях и функциях, замыканиях, итераторах
Структуры (structs) в Rust позволяют создавать пользовательские типы данных, объединяющие связанные значения и выступающие типом произведения.
Перечисления (enum) позволяют определить тип, перечисляя все возможные варианты значений.
Перечисления в Rust являются типами суммы, поскольку значение может быть одним из вариантов:
Тип Shape представляет объединение (сумму) всех возможных вариантов. И к этому есть мощное сопоставление с образцом для работы с ADT:
Модули позволяют организовать код и контролировать видимость элементов. Определяются они через синтаксис mod <name> {}
и могут быть вложенны друг в друга:
Также модули могут быть организованы в различных файлах (имя файла в таком случае будет именем модуля):
Импорт модуля осуществляется через ключевое слово use:
Функции в Rust определяются через ключевое слово fn
:
Можно делать функции высшего порядка и передавать в них, как обычные, так и анонимные функции:
Еще одним элементом функционального программирования в Rust выступает итератор:
Здесь мы создаем собственный итератор Counter, реализуя стандартный трейт Iterator. Он используется для многих встроенных коллекций, таких как Vec или HashMap. Этот трейт особенно удобен из-за различных функциональных комбинаторов из стандартной библиотеки:
Трейт Iterator
предоставляет множество методов адаптеров, таких как enumerate
, filter
или map
, которые возвращают новый итератор. Методы адаптеров ленивые, они не запускают итерацию. Также есть методы исполнители, которые итерируют пока не закончатся значения, например, collect
, fold
или count
.
Большинство коллекций (и ссылки на них) реализуют трейт IntoIterator (способность кастоваться в Iterator). Также IntoIterator автоматически реализуется для любого Iterator (ничего не стоящий каст сам в себя). Цикл for
в Rust работает только с объектами, реализующими IntoIterator
.
Как обрабатывать ошибки в Rust
В Rust принято разделять ошибки на 2 вида: паники и результаты операций:
- Паники используются для непредвиденных ситуаций и ошибок программиста, например, деление целочисленного типа на 0 или выход за границу массива. И хотя паники можно отловить, стандартное и рекомендуемое поведение при них — программа упадёт, будет напечатан стектрейс.
- Результаты операций выражаются типом
Result<T, E>
, который является перечислением из двух вариантов — Ok(T) и Err(E). Такой подход гарантирует, что все ошибки строго типизированы, а без обработки ошибки невозможно извлечь результат операции.
Option<T>
используется для представления значения, которое может отсутствовать:
Result<T, E>
используется для операций, которые могут завершиться ошибкой:
Для удобства проброса ошибок наверх существует оператор ?
, который пишется после любого выражения, возвращающего Result, и возвращает Ok вариант. В случае Err варианта будет выход из функции с возвращением ошибки.
Обычно все ошибки описываются в перечислениях, а для уменьшения шаблонного кода используются крейты вроде thiserror.
Про асинхронное программирование
Асинхронное программирование в Rust строится вокруг трейта Future— его реализуют для типов, представляющих значение, которое будет доступно в будущем.
Также в Rust есть синтаксис async/await
. Ключевым словом async
могут быть отмечены функции и блоки кода — они будут возвращать анонимный тип, реализующий Future. Async-блоки также могут захватывать окружение подобно замыканиям. Внутри async-блоков и функций возможно использовать ключевое слово await на любом выражении, возвращающем Future или IntoFuture (способность кастоваться к Future). В отличие от других языков с подобным синтаксисом, await записывается через точку после выражения, что очень удобно для построения цепочек вычислений.
Для исполнения асинхронного кода необходим рантайм, но стандартная библиотека такого рантайма не предоставляет, поэтому приходится использовать сторонние библиотеки. Самым популярным рантаймом является библиотека tokio.
В асинхронное программирование на Rust я рекомендую приходить уже после углубленного изучения языка, первых пет-проектов и небольшой работы с многопоточным кодом. Хотя синтаксис и общие правила работы с асинхронным кодом покажутся знакомыми тем, кто знает JS или C#, из-за более низкоуровневой природы языка работать с ним немного сложнее.
Что еще должен знать новичок в Rust
Вот примерный список:
- Очень желательно погрузиться в устройство памяти процесса, узнать, что помимо стека и кучи существуют и другие области (например, исполняемый машинный код так же отражён на память, а static-переменные хранятся не в стеке и не в куче, а в своей собственной области). Неплохо было бы и разобраться с тем, что у типов помимо размера есть выравнивание. Что в Rust бывают ZST (zero size type) — типы, размер которых честный 0, и DST (dynamic size type) — типы, размер которых неизвестен во время компиляции.
- Обязательно разобраться, как Rust освобождает память, не используя сборщик мусора. Почитать, что такое RAII. Понять, как работает трейт Drop.
- Избавится от стереотипов о Rust. Rust — не самый сложный язык, как только вы поймёте, как он работает. Плюс платят за Rust, как правило, больше, чем на аналогичных позициях на других языках.
- Оставить свои привычки из других языков (за исключением разве что Haskell/OCaml). Здесь не получится писать, как на C++/Java/Go и т.д. Привыкайте к хорошему и станете лучше, чем были до освоения Rust.
Что изучать, если есть вся база: чек-лист
- Макросы и метапрограммирование
- Unsafe Rust для низкоуровневого контроля
- Интеграция с C/C++ через FFI
- Разработка встраиваемых систем
- WebAssembly
- rustnomicon
- Undefined Behavior
- unsafe-код
Тренды на 2025 год
На Rust’е пишут все. Более полный список можно посмотреть тут: https://github.com/rust-unofficial/awesome-rust
Вот несколько топовых фреймворков и библиотек:
- serde — фреймворк для сериализации/десериализации
- tokio, futures — для асинхронного программирования
- clap — парсер аргументов командной строки
- anyhow, thiserror — удобная работа с ошибками
- chrono — работа с датой и временем
- dashmap — многопоточная hashmap
- bytes — эффективная работа с сырыми байтами
- log, tracing — для логирования
- reqwest — для http запросов
- axum — для http сервера и REST api
- mockall, test-case — упростит написание тестов
- bevy — игры
- clippy — линтер
А вы пишете на Rust?
Да, уже хорошо знаю базу
Разрабатываю на Rust уже больше года
Хочу начать!
915 открытий5К показов