Rust для чего нужен
Rust очень любят, но что в нём особенного?
Авторизуйтесь
Rust очень любят, но что в нём особенного?
Rust оказывается самым любимым языком по версии StackOverflow шесть лет подряд. В чём его секрет? Короткий ответ — Rust избавлен от болевых точек, которые есть во многих современных языках программирования.
Мы рассмотрим несколько примеров того, как Rust справляется с проблемами других языков и его недостатки, которые тоже присутствуют.
Наследник динамически типизированных языков
Споры между программистами, которые предпочитают динамическую типизацию статической, вероятно, будут продолжаться еще десятилетия, но трудно спорить о преимуществах статических типов.
Развитие таких языков, как TypeScript и наличие таких возможностей, как аннотации типов в Python, свидетельствуют о том, что люди разочаровались в текущем состоянии динамической типизации.
Статически типизированные языки позволяют использовать ограничения на данные и их поведение, проверяемые компилятором, что снижает когнитивные издержки и недопонимание.
Однако не все способы статической типизации эквивалентны. Многие статически типизированные языки поддерживают концепцию NULL.
Это значит, что любое значение может отсутствовать, таким образом создавая второй возможный тип для каждого типа. Как Haskell и некоторые другие современные языки программирования, Rust реализует эту возможность с помощью типа optional, и компилятор требует, чтобы вы указывали case None.
Это предотвращает возникновение ошибки: TypeError: Cannot read property ‘foo’ of null во время выполнения программы, вместо этого ошибка появляется ещё во время компиляции, и вы можете устранить её до того, как пользователь ее увидит. Вот пример функции для приветствия кого-либо независимо от того, знаем мы его имя или нет; если бы мы забыли случай None в match или попытались использовать имя так, как если бы оно было всегда присутствующим строковым значением, компилятор выдал бы ошибку.
Статическая типизация в Rust делает все возможное, чтобы не мешать программисту, при этом обеспечивая поддержку. Некоторые языки со статической типизацией ложатся большой нагрузкой на программиста, требуя многократного повторения типа переменной, что препятствует удобочитаемости и рефакторингу. Другие статически типизированные языки допускают вывод типа во всей программе.
Хотя это удобно в начале разработки, снижается способность компилятора предоставлять полезную информацию об ошибках, в случае несовпадения типов. Rust учится на обоих этих стилях и требует, чтобы элементы верхнего уровня, такие как аргументы функций и константы, имели явные типы, позволяя при этом выводить типы внутри тел функций. В этом примере компилятор Rust может определить тип дважды, 2 и 1, поскольку параметр val и возвращаемый тип объявлены как 32-разрядные целые числа со знаком.
Наследник языков со сборщиком мусора
Одним из самых больших преимуществ использования системного языка программирования является возможность контролировать низкоуровневые детали.
Rust позволяет выбирать между хранением данных в стеке или в куче и во время компиляции определяет что память больше не нужна и может быть очищена. Это позволяет эффективно использовать память. Tilde — один из первых пользователей Rust в своем продукте Skylight, обнаружил, что им удалось сократить использование памяти с 5 ГБ до 50 МБ, переписав некоторые конечные точки HTTP на Java в Rust. Такая экономия становится особенно значимой, когда облачные провайдеры меняют цены на дополнительную память.
Без необходимости постоянной работы сборщика мусора проекты Rust хорошо подходят для использования в качестве библиотек другими языками программирования через интерфейсы с внешними функциями. Это позволяет существующим проектам заменять критически важные для производительности части быстрым кодом на Rust без рисков для безопасности памяти, присущих другим системным языкам программирования. Некоторые проекты даже были постепенно переписаны в Rust с использованием этих методов.
Обладая прямым доступом к оборудованию и памяти, Rust является идеальным языком для разработки встраиваемых и bare-metal систем. Вы можете писать код чрезвычайно низкого уровня, например ядра операционной системы или приложения для микроконтроллеров. Основные типы и функции Rust, а также переиспользуемый библиотечный код отлично работают в этих особенно сложных средах.
Наследник языков системного программирования
Большинство людей рассматривают Rust как альтернативу таким языкам системного программирования, как Си или C++. Самое большое преимущество Rust — это проверка заимствований. Это часть компилятора, ответственная за то, чтобы ссылки не переживали данные, на которые они ссылаются, это помогает устранить целые классы ошибок, вызванных небезопасным использованием памяти.
В отличие от многих существующих языков системного программирования, Rust не требует, чтобы вы проводили все свое время, погружаясь в мельчайшие детали. Rust стремится иметь как можно больше абстракций с нулевой стоимостью — абстракций, которые столь же эффективны, как и эквивалентный рукописный код. В этом примере мы покажем, как итераторы, основная абстракция Rust, могут быть использованы для краткого создания вектора, содержащего первые десять квадратных чисел:
В тех случаях когда безопасного Rust недостаточно мы можете использовать небезопасный Rust. Это даёт дополнительные возможности, однако вы сами должны следить за тем, что код безопасен. Этот код затем может быть заключен в абстракции более высокого уровня, которые гарантируют, что все виды использования абстракции безопасны.
Использование небезопасного Rust должно быть обдуманным решением, поскольку его правильное использование требует столько же размышлений и осторожности, как и в любом другом языке, в котором вы несете ответственность за предотвращение неопределенного поведения. Сведение к минимуму небезопасного кода — лучший способ свести к минимуму возможности сбоев и уязвимостей из-за небезопасности памяти.
Языки системного программирования подразумевают, что они будут эффективно существовать вечно. В то время как некоторые современные разработки не требуют такого срока службы, многие компании хотят знать, что их фундаментальная база кода будет пригодна для использования в обозримом будущем. Rust признает это и принял сознательные дизайнерские решения, касающиеся обратной совместимости и стабильности; это язык, разработанный на ближайшие 40 лет.
Экосистема Rust
Rust больше, чем спецификация языка и компилятор; многие аспекты создания и поддержки программного обеспечения промышленного качества рассматриваются как объекты первого класса. С помощью rustup можно установить несколько параллельных цепочек инструментов Rust и управлять ими. Rust поставляются с Cargo — инструментом командной строки для управления зависимостями, запуска тестов, создания документации и многого другого. Поскольку зависимости, тесты и документация доступны по умолчанию, их использование широко распространено. crates.io — это сайт сообщества для обмена и поиска библиотек Rust. Любая библиотека, опубликованная в crates.io будет иметь свою документацию на docs.rs.
Кроме встроенных инструментов, коммьюнити Rust создало множество средств разработки. Бенчмаркинг, анализ и тестирование на основе свойств — все это легко легко использовать в проектах. Дополнительные линты компилятора доступны в Clippy, а автоматическое форматирование обеспечивается rustfmt. Поддержка IDE хороша и с каждым днем становится всё более эффективной.
Rust имеет яркое, гостеприимное сообщество. Существует несколько официальных и неофициальных способов получить помощь, таких как чат, форум, сабреддит Rust и, конечно же, Stack Overflow. У Rust есть кодекс поведения, который соблюдается потрясающей командой модераторов, поэтому официальные порталы и большинство неофициальных располагают к себе.
В оффлайне Rust проводит множество конференций по всему миру, таких как RustConf, Rust Belt Rust, RustFest, Rust Latam, RustCon Asia и другие.
Не всё так просто
Сильная система типов Rust и акцент на безопасность памяти — все это происходит во время компиляции — означают, что при компиляции кода чрезвычайно часто возникают ошибки. Это может быть неприятным для программистов, не привыкших к такому самоуверенному языку программирования. Тем не менее, разработчики Rust потратили большое количество времени на работу над улучшением сообщений об ошибках, чтобы убедиться, что они понятны и применимы.
Особенно часто можно услышать, как кто-то жалуется, что он «борется с проверкой заимствований». Хотя эти ошибки могут обескураживать, важно признать, что каждое из выявленных мест потенциально могло привести к ошибкам и потенциальным уязвимостям.
В этом примере мы создаем изменяемую строку, содержащую имя, затем берем ссылку на первые три байта имени. Мы пытаемся изменить строку, очистив ее. Теперь ссылки на действительные данные и их разыменование могут привести к неопределенному поведению, поэтому компилятор останавливает нас:
К счастью, сообщение об ошибке включает в себя наш код и изо всех сил пытается объяснить проблему, указывая точные места.
Прототипирование в Rust может быть сложными из-за его статически типизированной природы и из-за того, что Rust требует покрытия 100% условий. Крайние случаи должны быть описаны, даже если программист еще не знает, что там должно быть. На ранних стадиях разработки эти крайние случаи часто можно устранить, вызвав сбой программы, а затем можно добавить строгую обработку ошибок на более позднем этапе. Это другой рабочий процесс, он встречается в таких языках, как Ruby, где разработчики часто пробуют код в REPL, а затем переносят его в прототип, вообще не рассматривая случаи ошибок.
Rust все еще относительно новый язык, а это значит, что некоторые нужные библиотеки могут быть ещё недоступны. Положительным моментом является то, что есть много плодородной почвы для разработки этих необходимых библиотек, возможно, даже с использованием последних достижений в соответствующих областях компьютерных наук. Благодаря этому и возможностям Rust некоторые библиотеки Rust, такие как regex, являются лучшими в своем классе на любом языке.
Хотя Rust твердо привержен стабильности и обратной совместимости, это не означает, что язык доработан. Конкретная проблема может не решаться функциями языка, которые облегчили бы ее выражение или, возможно, даже позволили бы ее выразить. Например, в Rust асинхронные фьючерсы существуют уже более трех лет, но стабильная поддержка async/await появилась не так давно.
Зачем нам нужен Rust?
Rust — системный язык программирования, который исполняется чертовски быстро, предотвращает почти все падения, а также устраняет неопределённости совместного доступа к данным. Он разрабатывается Mozilla как инструмент для создания обозревателя нового поколения — Servo.
Пункты соприкосновения
Данное определение языка кажется сказкой, ибо доступные нам прежде инструменты всегда балансировали между скоростью и надёжностью. С одной стороны — С++, в котором огромные возможности и скорость компенсируются постоянными ошибками доступа вне выделенной памяти, к удалённой памяти, либо неожиданные результаты чтения данных, которые в это время пишет другой поток. С другой стороны есть Haskell, этакий язык-крепость (по принципу «раз оно компилируется, значит работает»), хоть и не могущий похвастаться скоростью. Где-то посередине балансируют Java, Python, C# и другие популярные (в силу своей практичности) языки. Rust же для меня выступает удачным скрещением лучших свойств С++ и Haskell, при этом сохраняя практичность на уровне конкурентов.
Через тернии к звёздам
Вся магия Rust становится возможной благодаря знанию компилятором о том, кто владеет определённой сущностью (owner), кто лишь временно одалживает её (mutable borrow), а кто просто пришёл посмотреть (immutable borrow). Программируя на С++ или Java, Вы всё равно держите в голове эту информацию, пусть и в несколько ином виде. В Rust это выражается языковыми конструкциями, что позволяет компилятору проверить правильность Вашей модели, а также гарантировать её беспроблемное выполнение. Для такого программирования требуется немного иной подход, чем мы привыкли. Попытаюсь пройтись по основным моментам, которые могут занести Вас в ступор при начальном изучении:
1. Нет наследования, зато есть структуры и способности (traits).
2. Указатели есть только в незащищённом коде (unsafe <>). Вместо них в безопасном коде есть ссылки, которые гарантированно указывают на существующие объекты.
3. Если у Вас неизменяемая ссылка на что-то (immutable borrow = &Object), то никто не может изменить значение, пока ссылка жива.
4. Если у Вас изменяемая ссылка (mutable borrow = &mut Object), то никто другой не может читать содержимое объекта, пока ссылка жива.
5. Разработчики языка предпочитают Mac и *nix, так что для работы под Windows нужна GNU среда.
У Rust очень весёлое и активное сообщество, Вам всегда будут рады на канале IRC и в Reddit. Уже написано приличное количество всякого добра, многие из проектов активно развиваются на GitHub. Особой популярностью язык пользуется у разработчиков игр и графики. Есть зачатки операционных систем. В перспективе также маячит возможность исполнения на веб-серверах и клиентах. Rust подходит для любых задач!
Единственная на сегодня, пожалуй, серьёзная проблема языка — это его бурное развитие. От версии к версии может меняться синтаксис, порой приходится переосмысливать логику, подстраиваясь под новые возможности языка. Такая ситуация продлится ещё какое-то время в этом году, пока не появится Rust-1.0. А тем временем журнал Rust ‘n Stuffs в еженедельной рубрике This Week in Rust оповещает нас обо всех прошедших и готовящихся изменениях, о новых статьях и перспективных проектах.
Немного обо мне
Скоро будет уже 2 года, как я перешёл на Rust, оставив позади Boo, Dart, Haskell и, конечно, С++. Первым проектом была игра, которую решил отложить до выхода стабильной версии языка. Сейчас активно работаю над rust-compress и своим компрессором данных. Верю в светлое будущее Rust и приглашаю всех принять участие!
10 неочевидных преимуществ использования Rust
Rust — это молодой и амбициозный язык для системного программирования. В нем реализовано автоматическое управление памятью без сборщика мусора и прочих накладных расходов времени исполнения. Кроме этого, в языке Rust используется семантика перемещения по умолчанию, имеются беспрецендентные правила обращения к изменяемым данным, а также учитываются времена жизни ссылок. Это позволяет ему гарантировать безопасность памяти и облегчает многопоточное программирование, ввиду отсутствия гонок данных.
Все это уже хорошо известно всем, кто хоть немного следит за развитием современных технологий программирования. Но что если вы не системный программист, да и многопоточного кода в ваших проектах не много, но вас все же привлекает производительность Rust’а. Получите ли вы какие-то дополнительные преимущества от его использования в прикладных задачах? Или все, что он вам даст дополнительно — это суровую борьбу с компилятором, который будет заставлять вас писать программу так, чтобы она неотступно следовала правилам языка по заимствованию и владению?
В данной статье собран десяток неочевидных и особо не рекламируемых преимуществ использования Rust, которые, я надеюсь, помогут вам определиться с выбором этого языка для ваших проектов.
1. Универсальность языка
Несмотря на то, что Rust позиционируется как язык для системного программирования, он подходит и для решения высокоуровневых прикладных задач. Вам не придется работать с сырыми указателями, если для вашей задачи это не нужно. В стандартной библиотеке языка уже реализовано большинство типов и функций, которые могут понадобиться в прикладной разработке. Также можно легко подключать внешние библиотеки и использовать их. Система типов и обобщенное программирование в Rust позволяют использовать абстракции достаточно высокого уровня, хотя прямая поддержка ООП в языке отсутствует.
Давайте рассмотрим несколько простых примеров использования Rust.
Пример совмещения двух итераторов в один итератор по парам элементов:
Пример использования внешней библиотеки regex для работы с регулярными выражениями:
Пример использования обобщенного типа в структуре:
На Rust вы можете писать эффективные системные утилиты, большие настольные приложения, микросервисы, веб-приложения (включая клиентскую часть, так как Rust можно скомпилировать в Wasm), мобильные приложения (хотя в этом направлении экосистема языка пока развита слабо). Такая универсальность может оказаться преимуществом для многопроектных команд, потому что она позволяет использовать одинаковые подходы и одни и те же модули во множестве разных проектов. Если вы привыкли к тому, что каждый инструмент предназначен для своей узкой области применения, то попробуйте посмотреть на Rust как на ящик с инструментами одинаковой надежности и удобства. Возможно, вам именно этого и не хватало.
2. Удобные инструменты сборки и управления зависимостями
Это явно не рекламируется, но многие замечают, что в Rust реализована одна из лучших на сегодняшний день система сборки и управления зависимостями. Если вы программировали на С или С++, и вопрос безболезненного использования внешних библиотек стоял для вас достаточно остро, то использование Rust с его инструментом сборки и менеджером зависимостей Cargo будет хорошим выбором для ваших новых проектов.
Кроме того, что Cargo будет за вас загружать зависимости и управлять их версиями, собирать и запускать ваши приложения, выполнять тесты и генерировать документацию, дополнительно он может быть расширен плагинами и для других полезных функций. Например, существуют расширения, позволяющие Cargo определять устаревшие зависимости вашего проекта, производить статический анализ исходного кода, собирать и редеплоить клиентские части веб-приложений и многое другое.
Конфигурационный файл Cargo использует для описания настроек проекта дружелюбный и минималистичный язык разметки toml. Вот пример типичного файла конфигурации Cargo.toml :
А ниже приведены три типичные команды использования Cargo:
С их помощью будет произведена проверка исходного кода на ошибки компиляции, сборка проекта и запуск тестов, сборка и запуск программы на выполнение, соответственно.
3. Встроенные тесты
Модульные тесты в Rust писать настолько легко и просто, что хочется это делать снова и снова. 🙂 Зачастую вам будет проще написать модульный тест, чем пытаться протестировать функциональность другим способом. Вот пример функций и тестов к ним:
Особого внимания заслуживают исполняемые как тесты примеры документации, но об этом будет сказано ниже.
Встроенные тесты производительности (бенчмарки) тоже имеются, но они пока не стабилизированы, поэтому доступны только в ночных сборках компилятора. В стабильном Rust для этого вида тестирования придется использовать внешние библиотеки.
4. Хорошая документация с актуальными примерами
Стандартная библиотека Rust очень хорошо документирована. Html-документация генерируется автоматически по исходному коду с markdown-описаниями в док-комментариях. Более того, док-комментарии в коде на Rust содержат примеры кода, которые исполняются во время запуска тестов. Этим гарантируется актуальность примеров:
Здесь пример использования метода as_bytes у типа String
будет выполнен как тест во время запуска тестов.
Кроме этого, для Rust-библиотек распространена практика создания примеров их использования в виде небольших самостоятельных программ, расположенных в директории examples в корне проекта. Эти примеры также являются важной частью документации и они также компилируются и выполняются во время прогона тестов, но их можно запускать и независимо от тестов.
5. Умное автовыведение типов
В программе на Rust можно явно не указывать тип выражения, если компилятор в состоянии его вывести автоматически, исходя из контекста использования. Причем это касается не только тех мест, где объявляются переменные. Давайте рассмотрим такой пример:
Если мы расставим аннотации типов, то данный пример будет выглядеть так:
Такая система автовыведения типов избавляет код от лишнего шума и делает его таким же лаконичным, как и код на каком-нибудь динамически типизированном языке программирования. И это при сохранении строгой статической типизации!
Конечно, мы не можем польностью избавиться от указания типов в статически типизированном языке. В программе должны быть точки, в которых типы объектов гарантированно известны, чтобы в других местах можно было эти типы выводить. Такими точками в Rust являются объявления пользовательских типов данных и сигнатуры функций, в которых нельзя не указывать используемые типы. Но в них можно вводить «метапеременные типов», при использовании обобщенного программирования.
6. Сопоставление с образцом в местах объявления переменных
на самом деле не ограничивается только объявлением новых переменных. То, что она делает на самом деле — это осуществляет сопоставление выражения справа от знака равенства с образцом слева. А новые переменные могут быть введены в составе образца (и только так). Взгляните на следующий пример, и вам станет понятнее:
Другой популярный пример использования образца в цикле for :
Или при сопоставлении в операторе match :
Сопоставление с образцом делает код весьма компактным и выразительным, а в операторе match оно вообще незаменимо. Оператор match — это оператор полного вариативного анализа, поэтому случайно забыть в нем проверить какое-то из возможных совпадений для анализируемого выражения у вас не получится.
7. Расширение синтаксиса и пользовательские DSL
Синтаксис языка Rust ограничен, во многом из-за сложности используемой в языке системы типов. Например, в Rust отсутствуют именованные аргументы функций и функции с переменным числом аргументов. Но можно обойти эти и другие ограничения с помощью макросов. В Rust существует два вида макросов: декларативные и процедурные. С декларативными макросами у вас никогда не будет таких же проблем, как с макросами в С, потому что они гигиеничны и работают не на уровне текстовой замены, а на уровне замены в абстрактном синтаксическом дереве. Макросы позволяют создавать абстракции на уровне синтаксиса языка. Например:
Помимо того, что данный макрос расширияет синтаксические возможности вызова «функции» печати форматированной строки, он еще будет в своей реализации проверять соответствие входных аргументов указанной строке формата во время компиляции, а не во время выполнения. С помощью макросов вы можете вводить лаконичный синтаксис под ваши собственные проектные нужды, создавать и использовать DSL. Вот пример использования кода на JavaScript внутри Rust-программы, компилирующейся в Wasm:
Макрос js! определен в пакете stdweb и он позволяет встраивать полноценный JavaScript-код в вашу программу (за исключением строк в одинарных кавычках и операторов, не завершенных точкой с запятой) и использовать в нем объекты из Rust-кода с помощью синтаксиса @
Макросы открывают огромные возможности по адаптации синтаксиса Rust-программ к специфическим задачам конкретной предметной области. Они сэкономят ваше время и внимание при разработке сложных приложений. Не за счет увеличения накладных расходов времени выполнения, но за счет увеличения времени компиляции. 🙂
8. Автогенерация зависимого кода
Процедурные derive-макросы в Rust широко используются для автоматической реализации типажей и прочей кодогенерации. Вот пример:
Здесь с помощью derive-макросов Serialize и Deserialize из библиотеки serde для структуры Point автоматически генерируются методы ее сериализации и десериализации. Дальше можно передавать экземпляр этой структуры в различные функции сериализации, например, преобразующие его в JSON строку.
9. Алгебраический тип данных
Алгебраический тип данных, говоря упрощенно — это составной тип данных, являющийся объединением структур. Более формально — это тип-сумма из типов-произведений. В Rust такой тип определяется с помощью ключевого слова enum :
Выяснить, какой действительно тип приняло значение в конкретном случае можно с помощью сопоставления с образцом:
В Rust отсутствует null-значение, ровно как и досадные ошибки непредвиденного обращения к нему. Вместо этого там, где действительно необходимо указать возможность отсутствия значения, используется Option :
Алгебраический тип данных — достаточно мощный и выразительный инструмент, который открывает дверь в Type-Driven Development. Грамотно написанная программа в этой парадигме возлагает на систему типов большую часть проверок корректности своей работы. Поэтому если вам нехватает немного Haskell в повседневном промышленном программировании, Rust может стать вашей отдушиной. 🙂
10. Легкий рефакторинг
Развитая строгая статическая система типов в Rust и попытка выполнить как можно больше проверок во время компиляции, приводит к тому, что дорабатывать и рефакторить код становится доcтаточно просто и безопасно. Если после изменений программа собралась, то это значит, что в ней остались только логические ошибки, не связанные с тем функционалом, проверка которого была возложена на компилятор. В сочетании с легкостью добавления модульных тестов для проверки логики, это приводит к серьезным гарантиям надежности программ и росту уверенности программиста в корректной работе своего кода после внесения изменений.
Пожалуй это все, о чем я хотел рассказать в этой статье. Конечно, у Rust есть еще много других достоинств, а также имеется ряд недостатков (некоторая сырость языка, отсутствие привычных идиом программирования, «нелитературный» синтаксис), о которых здесь не упоминается. Если вам есть, что о них рассказать — напишите в комментариях. А вообще, опробуйте Rust на практике. И может быть его достоинства для вас перевесят все его недостатки, как это произошло в моем случае. И вы, наконец, получите именно тот набор инструментов, в котором долго нуждались.