Rust — язык, на который переписывают en ru
Rust медленно, но неотвратимо, перерабатывает кодовую базу человечества.
Лет 5 назад я изучал документацию Rust и решил, что Rust мне не нравится.
В прошлом году мне потребовалось прототипировать игровую логику и я выбрал для этого Rust, так как ничего лучше не нашёл: Zig выглядел сырым, а C++ с каждым стандартом становится всё мертвее сложнее и сложнее.
В итоге у меня накопилось 10 страниц заметок, которые неожиданным образом ужались в очень компактное утверждение в заголовке. И если вам лень читать дальше, то это похвала, а не критика.
Однако, я всё ещё придерживаюсь своего мнения из предыдущего поста:
Хороший инструмент не ограничивает своего пользователя, поскольку разработчик инструмента никогда не предугадает все варианты использования.
Согласно моим представлениям о прекрасном о том, как работают технологии, вероятность успеха языка программирования, построенного на принципах Rust, стремится к нулю. Однако de facto ситуация совершенно противоположная — Rust стремительно набирает популярность и поработав с ним, я признаю, что это хороший и мощный инструмент. Но, ё-моё, неправильный, ну кто ж так делает-то.
На мой взгляд, успех Rust — это следствие двух факторов:
- Огромного профессионализма и опыта его создателей и core-команды.
- Зрелости индустрии разработки софта.
Я попробую кратко описать главное концептуальное отличие Rust от других языков, почему оно одновременно является его силой и слабостью и почему уже несколько лет идёт волна переписывания всего на Rust.
Этот пост основан на строго субъективном опыте
Я использовал Rust только год; для прототипирования и экспериментирования; в геймдеве.
Соответственно, я никаким образом не могу претендовать на экспертность в языке и иметь сколь-нибудь объективное мнение на его счёт.
Например, некоторые сложности, которые я испытывал, могли быть вызваны тем, что я одгновременно прототипировал и учил язык. То есть следствием моего выбора, а не свойств языка.
В то же время у меня достаточно опыта разработки, чтобы экстраполировать этот год на более общие случаи и сформировать своё субъективное мнение о Rust. Этим я в этом эссе и займусь.
Цена композиции
Композиция выигрывает у наследования в большинстве общих случаев за счёт лучшей гибкости, большей вариативности, меньшей связанности и, как следствие, лучшей поддерживаемости результата.
Поэтому большинство языков программирования организованы вокруг ортогональных правил/механизмов/компонентов, которые разработчики могут комбинировать и использовать по своему усмотрению.
Вы не платите за то, что не используете
С древних времён так повелось, что подход к композиции фич языка предполагает принцип «вы платите только за то, что используете». Например, в C++ это даже в ключевые принципы дизайна языка вынесено: What you don't use, you don't pay for — это часто трактуют как требование к производительности, но, на мой взгляд, это касается и ментальной нагрузки на программиста.
Например:
- В C++, если вы не используете исключения, то вам не надо думать о том, как они работают, не надо использовать их синтаксис в коде, не надо предусматривать специальные оптимизации для них и так далее.
- В Python, если вы не хотите использовать классы, то вы просто не используете классы.
Некоторые языки частично отходят от этого принципа. Например, исторически (в 2025 году требование ослабили), Java требовала от вас использовать классы, даже если вам не нужны их возможности, но это скорее доведение одной из фич до абсолюта, чем отказ от самого принципа.
Следствием этого подхода является то, что фичи языка не ограничивают применение друг друга. Например, в C++ вы можете использовать классы, исключения, шаблоны, лямбды и так далее в любом сочетании, которое вам нужно.
Компонуя фичи языка без ограничений, программист может создавать очень эффективные, мощные и гибкие системы — если он профессионал. Или создавать забагованные системы с миллионом дыр в безопасности, если с профессионализмом не задалось.
Более того, и это важно, при отсутствии ограничений на композицию фич становится намного проще экспериментировать — пробовать разные архитектурные решения, паттерны, стили программирования, etc.
Вы будете платить за всё
Rust пошёл другим путём.
Его механизмы всё ещё ортогональны друг другу, но только в плане ответственности за что-то, а не в возможности включать/выключать их использование или комбинировать их любыми способами. Вместо этого они требуют от вас использовать их в связке с другими механизмами языка в конкретно определённых случаях. Вместо кирпичиков лего вы получаете намертво сваренную железную решётку, в которую необходимо вписать архитектуру и код. Узлы такой решётки становятся глобальными инвариантами, которые невозможно нарушить, подвинуть или обойти.
Например, вы не можете «просто не использовать lifetime» (подставьте сюда borrow checker, traits, etc.) — вы должны либо использовать его, либо строить свою архитектуру очень специфическим образом вокруг него (что может и не получиться).
Это разительно снижает вариативность — ограничивает возможность экспериментировать, но, одновременно, ограничивает возможность ошибаться.
Наличие глобальных инвариантов навязывает определённые архитектурные решения, которые во многих случаях можно считать best practices. Rust заставляет создавать более хорошую, правильную, надёжную архитектуру независимо от вашей потребности в ней.
Для продуктивной работы, программист на Rust должен одновременно держать в голове все механизмы языка: стандартные traits, правила lifetime, правила borrow checker, etc. Причём держать в голове их надо не по отдельности, а с учётом всех их взаимодействий и ограничений на композицию.
Вход в язык для профессионалов и новичков
Необходимость держать в памяти весь язык со всеми нюансами создаёт большой порог входа в него.
На мой взгляд, этим объясняется и распространённое мнение, что новичку войти в язык проще чем профессионалу перейти с другого языка.
Новичок сразу учится думать в рамках ограничений. Профессионал уже имеет опыт композиции кода без ограничений и он начинает спотыкаться о запреты инварианты Rust на каждой строке кода.
Удобство и полезность языка, созданного на таких принципах, в первую очередь будет определяться качеством выбранных абстракций и качеством реализации стандартной библиотеки. «Одна ошибка и ты ошибся» — люди не смогут использовать твой язык для реальных задач.
У Rust это каким-то образом получилось.
Я не до конца понимаю как. На мой взгляд, кроме крутости его создателей, это однозначно свидетельствует о зрелости индустрии разработки софта — большая часть софта стала максимально шаблонной и под эти шаблоны удалось создать множество инвариантов, которое позволяет их надёжно реализовать.
Почему всё переписывают на Rust
Моя гипотеза — человечество закончило фазу экспериментирования с "баовой" программной инфраструктурой и переходит в фазу её стабилизации.
Мы думаем что Мы «знаем» на каких принципах должны работать sudo, coreutils, web-серверы, базы данных, пользовательские интерфейсы, etc.
Меньше необходимости экспериментировать с архитектурой и стилем программирования — меньше давление на выбор языков со свободной композицией фич.
Больше необходимости в надёжности, безопасности и предсказуемости — больше давление на выбор языков с ограничениями на композицию фич.
Rust же даёт не только надёжность, но и удобную семантику для описания стандартных/правильных/безопасных архитектур.
Это выливается сразу в два «движения»:
- Снизу — классическое «я могу сделать лучше», я построю свой велосипед с блэкджеком и повышенной надёжностью приводит к тому, что энтузиасты выбирают Rust для
переписываниясоздания аналогов софта, так как Rust не мешает «сделать правильно» и защищает от попыток «сделать неправильно». - Сверху — корпорации начинают переводить инфраструктуру на Rust, так как ценность экспериментирования с кодом снижается, а издержки от багов и уязвимостей растут. Зачем экспериментировать, если всё уже стандартизировано и понятно, примерно, как оно должно работать согласно лучшим практикам?
Сам Rust движется примерно в эту же сторону. Например, вот цитата одного из core-разработчиков Rust из его поста про место Rust в 2025:
I see Rust's mission as making it dramatically more accessible to author and maintain foundational software. By foundational I mean the software that underlies everything else.
Foundational software — это широкий и расплывчатый термин, но, по сути, это софт на уровнях ниже тех, что видит обычный пользователь; софт, который предоставляет базовую ожидаемую/стандартную функциональность для всего, что мы строим поверх него.
Преимущества Rust
Во-первых, Rust подталкивает вас создавать надёжный стандартный софт следуя подмножеству лучших практик.
В теории, под это попадает большая часть коммерческого софта и существенная часть софта с открытым исходным кодом.
Я, например, задумываюсь не переключить ли мне свою веб-специализацию с Python на Rust, так как шаблоннее современного веба сложно что-то придумать.
Во-вторых, из-за ограничений на вариативность архитектуры Rust должен хорошо подходить для разработки через coding agents — значительно сужается пространство для ошибки (если вы запретите unsafe).
В-третьих, как следствие вынужденного следования лучшим практикам, стандартная библиотека Rust и ядро его экосистемы довольно хороши.
На этом эту секцию можно закончить. Получается, что Rust — это этакая рабочая лошадка чтобы делать рутинную инженерную работу.
Давайте лучше поговорим о недостатках Rust.
Недостатки Rust
Проблемы Rust происходят оттуда же, откуда и его преимущества — от решётки жёстких инвариантов.
Высокий порог входа
Вы не можете выбрать подмножество языка и начать на нём делать более-менее сложный проект — вам нужно держать в голове весь язык.
В противном случае вы будете методично упираться в стену, изучать что-то новое и переделывать свою работу, пока опять не упрётесь в стену.
Это тяжело для опытных программистов, потому что многие из них (включая меня) привыкли изучать вещи методом научного тыка. Особенно, когда на кону нет человеческих жизней, финансовых потерь и прочих страшных вещей.
К слову, по моим наблюдениям, Rust стал последним языком, с которым были трудности у LLM.
Дорого прототипировать и экспериментировать
Классическое прототипирование предполагает, что весь прототип или его часть выкидывается после проверки гипотезы.
Скорость получения видимого результата на порядки важнее качества, надёжности — чего угодно. Поэтому при прототипировании мы всегда максимально срезаем углы, пользуясь знаниями о том, как прототип будет использоваться и видением всей прототипируемой системы.
Например, если я делаю библиотеку, которую буду использовать только я и только в однопоточном режиме, я вообще не хочу задумываться о синхронизации, нарушении инвариантов мультипоточности, etc. Я не хочу знать как Rust делает многопоточность, я не хочу учить как он это делает и я не хочу прописывать что-то для этого в моём коде — этот код будет выброшен через месяц. Я просто хочу написать код, который работает в нескольких конкретных случаях, и проверить гипотезу. Независимо от того, что используемые мной сторонние библиотеки или стандартные контейнеры рассчитаны на использование во многопоточных сценариях.
Или вот я не хочу думать кто/что владеет данными, потому что абсолютно уверен, что вот конкретно в этом куске системы это абсолютно не важно, а, если даже важно, то не повлияет на полезность прототипа.
В Rust срезать углы сложно, даже с unsafe. Например, unsafe не снимает ограничения на контроль времени жизни данных.
На мой взгляд, именно поэтому разработка игр на Rust всё ещё не получила широкого распространения. Gamedev — это одна из немногих оставшихся областей, где прототипирование и экспериментирование всё ещё важны и, возможно, останутся важными в обозримом будущем.
Поэтому рекомендую прототипировать не на Rust, а потом переписывать результат на Rust, когда уже понятно как этот результат должен работать. Вот, например, человек спрототипировал файловую систему на Python, а потом переписал её на Rust.
Сложно варьировать качество кода
Логика примерно та же, что и с прототипированием, но в данном случае речь о том, что разные части одного проекта могут иметь разные требования к качеству.
Для примера, условно говоря, ваше решение по lifetime данных будет влиять на все места, где вы эти данные используете.
Определённое варьирование возможно, например, в том насколько параноидально обрабатывать ошибки, но в целом, говнокодить в одной части и писать красивую архитектуру в другой будет сложнее.
Rust подталкивает к overengineering
Поскольку Rust мешает писать простой плохой код и, одновременно, даёт инструменты для написания сложного хорошего кода, получается, что он сдвигает баланс в сторону более сложного кода.
А как известно, нет предела совершенству. Играть трейтами, организовать уровни абстракций удобно и увлекательно — это как решать олимпиадные задачки.
Вот цитата от разработчика, который 4 года писал на Rust, из поста Everybody's so Creative!:
I love the language – but I'm starting to think the ecosystem has an abstraction addiction. Or: why every Rust crate feels like a research paper on abstraction.
У меня ровно такие же впечатления.
Возможные проблемы в будущем
C++ упёрся в требование тотальной обратной совместимости и закостенелость комитета.
Rust может стать жертвой своей же жёсткости, так как она ограничивает не только ваш код, но и дизайн языка. Чем больше жёстких связей между его механизмами, тем сложнее вносить изменения в язык и тем сложнее добавлять новые механизмы, так как они должны быть согласованы с уже существующими и не нарушать их.
Добавление механизма, который жёстко связан со всеми остальными, будет автоматически приводить к квадратичному росту когнитивной сложности языка.
Это уже приводит к огромным задержкам при добавлении новых механизмов:
- Добавление Generic associated types потребовало больше 6 лет.
- Задаче на специализацию генериков уже 10 лет.
Более того, текущие правила traits уже частично ограничивают потенциальное развитие экосистемы библиотек. Из-за того, как работают traits, происходит очень сильная привязка экосистемы к набору ключевых third-party библиотек, например, к serde — фреймворку сериализации. Если в будущем кто-то создаст более удобный фреймворк, перейти на него будет очень сложно, так как потребуется прописывать его поддержку во всех библиотеках экосистемы (как это сейчас сделано для serde). Само собой, такая библиотека не одна и получается, что жёсткость Rust приводит к такой же жёсткости экосистемы. Если ничего не поменять, то в будущем это может привести к стагнации языка.
Вот отличный пост про проблему и как её пытаются решать: An Incoherent Rust.
Влияние Rust на надёжность софта может быть преувеличено
В посте In Defense of C++ Dayvi Schuster приводит сильный аргумент:
…any rewrite of an existing codebase is going to yield better results than the original codebase.
То есть надёжность и безопасность софта, который пишут на Rust, может быть не только следствием его семантики, но и того, что:
- Мы в целом начали писать софт более качественно, выбирать более правильную архитектуру.
- В случае переписывания на Rust, все существенные ошибки уже сделаны разработчиками оригинального софта, а архитектура уже проверена временем, поэтому переписывающим разработчикам достаётся изначально менее сложная и рисковая задача.
Rust выдвигает высокие требования к способностям программиста
Команда разработчиков на Rust будет крутой и дорогой — этот вариант подходит не всем компаниям и не всем проектам. Ошибки в архитектуре софта на Rust могут происходить реже, но могут стоить существенно дороже из-за потенциальной «фундаментальности» на уровне проекта.
ИИ, в теории, может снизить требования к уровню разработчиков, но пока что это, скорее, теория. Мой личный опыт с coding agents говорит, что агенты отлично справляются с кодом, но очень плохо с архитектурой и долгосрочным планированием. Большинство проблем в проекте на Rust будет как раз из-за ошибок в архитектуре.
Мои личные впечатления и планы на Rust
- Как человеку склонному к перфекционизму, мне Rust нравится, потому что он заставляет писать правильный код.
- Как бывшему олимпиаднику, мне Rust нравится, потому что он даёт решать прикольные задачки. Примерно уровня задач на шаблоны в C++ — по ощущениям одно и то же. Только в C++ ты решаешь такие задачи, только когда работаешь с шаблонами, а в Rust — всегда.
- Идеологически мне Rust не нравится, так как он ограничивает свободу программиста.
- В краткосрочной и среднесрочной перспективе Rust, скорее всего, будет отъедать рынок стандартного/foundational/шаблонного софта, что бы «стандартный» и «foundational» в этом случае ни значили.
- В долгосрочной перспективе я вижу риск для Rust закопать себя точно так же, как себя закапывает C++ последние 20 лет.
Поэтому сейчас я настроен переключиться на Rust, если появится такая возможность/необходимость. Но в то же время я в любой момент буду готов с него соскочить на что-то более гибкое.
Полезные ссылки
Если вы хотите копнуть немного глубже, то вот несколько интересных ссылок:
- Собрание реальных фактов, когда компании решали переходить на Rust или отказывались от него.
- Примеры реальных проблем Rust.
- Визуализация сложности borrow-checker.
- Крутейшее объяснение работы traits, с картинками
Читать далее
- Мысли о языках программирования
- Модная типизация в Python
- Генерация подземелий — от простого к сложному
- Два года пишем RFC — статистика
- О проектировании миров
- Автоматический генератор нелинейных квестов
- World Builders 2023: Считаем бизнес-план для игры в Steam
- Жизнь и работа с ошибками
- Миграции backend на практике
- Типы в Python не радуют