Хабрахабр

[Перевод] Вышел Rust 2018… но что это такое?

Статья написана Лин Кларк в сотрудничестве с командой разработчиков Rust («мы» в тексте). Можете прочитать также сообщение в официальном блоге Rust.

В этом релизе мы сосредоточились на производительности, чтобы разработчики Rust стали работать максимально эффективно. 6 декабря 2018 года вышла первая версия Rust 2018.

Она окружена значками для инструментов и четырёх областей: WebAssembly, embedded, networking и CLI.
Временнáя шкала показывает переход функций из бета-версии в Rust 2018 и Rust 2015. Красный круг — эффективность разработчика — окружает всё, кроме Rust 2015

Но вообще непросто объяснить, что такое Rust 2018.

Я говорю «не совсем», потому что здесь «новая версия» означает не то, что новые версии других языков.
В большинстве других языков все новые функции добавляют новую версию. Некоторые представляют его новой версией языка… примерно так и есть, но не совсем. Предыдущая версия не обновляется.

Это связано с тем, как развивается язык. Система Rust действует иначе. Они не требуют каких-либо изменений. Почти все новые функции на 100% совместимы с Rust. Новые версии компилятора продолжат поддерживать “Rust 2015 mode” по умолчанию. Это означает, что нет причин ограничивать их кодом Rust 2018.

И этот новый синтаксис может сломать существующие базы кода. Но иногда развитие языка требует инноваций, например, нового синтаксиса.

Изначально в Rust не было таких понятий. Например, функция async/await. Но оказалось, что данные примитивы действительно полезны, они упрощают написание асинхронного кода.

Но следует действовать с осторожностью, чтобы не сломать старый код, где async или await могли использоваться как имена переменных. Для этой функции необходимо добавить ключевые слова async и await.

Хотя функция ещё не вышла, ключевые слова теперь зарезервированы. Таким образом, мы добавляем ключевые слова в Rust 2018. 31. Все несовместимые изменения на ближайшие три года разработки (например, добавление новых ключевых слов), вносятся единовременно в Rust 1.

Даже при наличии переменных async и await код будет компилироваться. Хотя в Rust 2018 есть несовместимые изменения, это не значит, что ваш код сломается. По умолчанию компилятор работать как раньше.

Команда cargo fix скажет, если нужно обновить код для использования новых функций и автоматизирует процесс внесения изменений. Но если хотите использовать одну из новых функций, то можете выбрать новый режим компиляции Rust 2018. Затем можете добавить edition=2018 к своему Cargo.toml, если согласны на использование новых функций.

Он ограничен одним конкретным крейтом. Этот спецификатор версии в Cargo.toml не применяется ко всему проекту и не относится к вашим зависимостям. То есть можно одновременно использовать крейты Rust 2015 и Rust 2018.

Большинство изменений внедряются одновременно в Rust 2018 и Rust 2015. Поэтому даже при использовании Rust 2018 всё выглядит примерно так же, как Rust 2015. Только несколько функций требуют несовместимых изменений.

Далеко не только они. Rust 2018 — это не только изменения основного языка.

Rust 2018 — это в первую очередь толчок для повышения производительности разработчиков Rust, во многом благодаря инструментам, которые находятся за пределами языка, а также благодаря отработке конкретных применений и пониманию того, как сделать Rust самым эффективным языком программирования для этих случаев.

Таким образом, вы можете представлять Rust 2018 как спецификатор в Cargo.toml, который используется для включения нескольких функций, требующих несовместимых изменений…

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

Итак, давайте посмотрим на все усовершенствования, сделанные за пределами языка, а затем погрузимся в сам язык. Мы предпочитаем второй вариант определения.

Язык программирования не может быть эффективным сам по себе, абстрактно. Он эффективен при конкретном применении. Поэтому мы понимали, что нужно не просто улучшить Rust как язык или инструмент. Нужно ещё и упростить использование Rust в определённых областях.

В других случаях — полировку уже существующих функций и хорошую документацию, чтобы стало легче поднять и запустить рабочую систему. В некоторых случаях это означало создание совершенно нового набора инструментов для совершенно новой экосистемы.

Команда разработчиков Rust сформировала рабочие группы по четырём направлениям:

  • WebAssembly
  • Встраиваемые приложения
  • Сетевые задачи
  • Инструменты командной строки

WebAssembly

Для WebAssembly пришлось создать совершенно новый набор инструментов.

С тех пор Rust быстро стал лучшим языком для интеграции с существующими веб-приложениями. Только в прошлом году WebAssembly сделала возможным компиляцию для запуска в интернете таких языков, как Rust.

Rust хорошо подходит для веб-разработки по двум причинам:

  1. Экосистема крейтов Cargo работает так, как привыкло большинство разработчиков веб-приложений. Объединяете кучу небольших модулей, чтобы сформировать более крупное приложение. Это значит, что Rust легко использовать именно там, где нужно.
  2. Rust потребляет мало ресурсов и не требует среды выполнения. Не нужно много кода. Если у вас крошечный модуль, выполняющий много тяжёлой вычислительной работы, внедрите несколько строчек Rust, чтобы его ускорить.

С помощью крейтов web-sys и js-sys из кода Rust легко вызвать веб-API, такие как fetch или appendChild. И wasm-bindgen упрощает поддержку высокоуровневых типов данных, которые WebAssembly нативно не поддерживает.

Можете использовать wasm-pack, чтобы автоматически запустить эти инструменты, и запушить модуль в npm, если хотите. После написания модуля Rust WebAssembly есть инструменты, чтобы легко подключить его к остальной части веб-приложения.

в книге «Rust и WebAssembly». Подробнее см.

Что дальше?

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

Встраиваемые приложения

Для встроенной разработки необходимо было повысить стабильность существующей функциональности.

Это современный инструментарий, которого катастрофически не хватало разработчикам, и очень удобные языковые функции высокого уровня. Теоретически, Rust всегда был хорошим языком для встроенных приложений. Таким образом, Rust отлично подходит для embedded. Всё это без лишней нагрузки на CPU и память.

В стабильном канале отсутствовали необходимые функции. Но на практике выходило иначе. Это означает, что людям приходилось компилировать собственную версию крейта ядра Rust (крейт, который используется в каждом приложении Rust для обеспечения основных строительных блоков Rust — встроенных функций и примитивов). Кроме того, для использования на встроенных устройствах требовалось изменить стандартную библиотеку.

И в отсутствие автоматических тестов экспериментальная сборка часто не работала на микроконтроллерах. В итоге разработчики зависели от экспериментальной версии Rust.

Это означает, что изменение какого-нибудь десктопного компонента не сломает встроенную версию. Чтобы исправить это, разработчики постарались перенести все необходимые функции на стабильный канал, добавить тесты к системе CI для микроконтроллеров.

С такими изменениями разработка встроенных систем на Rust переходит из области передовых экспериментов в область нормальной эффективности.

в книге «Rust для встроенных систем». Подробнее см.

Что дальше?

В этом году Rust обзавёлся действительно хорошей поддержкой популярного семейства ARM Cortex-M. Тем не менее, многие архитектуры ещё не так хорошо поддерживаются. Необходимо расширить Rust для аналогичной поддержки других архитектур.

Сетевые задачи

Для работы в сети необходимо было встроить в язык ключевую абстракцию: async/await. Таким образом, разработчики могут использовать стандартные идиомы Rust даже в асинхронном коде.

Например, ответа на запрос. В сетевых задачах часто приходится ждать. Но в асинхронном коде такую функцию можно поставить в режим ожидания, а ядро CPU пока займётся остальным. Если код синхронный, то работа будет остановлена: ядро процессора, на котором выполняется код, не сможет ничего сделать, пока не придёт запрос.

В высокопроизводительных приложениях серверное приложение будет обрабатывать гораздо больше соединений на каждый сервер. Асинхронное программирование возможно и в Rust 2015, и в этом есть много преимуществ. Во встроенных приложениях на крошечных однопоточных CPU оптимизируется использование единственного потока.

Вот в чём польза async/await. Но эти плюсы сопровождаются главным недостатком: для такого кода не действует проверка заимствований и придётся использовать нестандартные (и немного путаные) идиомы Rust. Это даёт компилятору необходимую информацию для проверки заимствований асинхронных вызовов функций.

31, хотя в настоящее время не поддерживаются реализацией. Ключевые слова для async/await реализованы в версии 1. Бóльшая часть работы выполнена, и функция должна быть доступна в следующем релизе.

Что дальше?

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

Если превратить их в компоненты — общие абстракции, которые совместно используются как крейты — тогда будет легко подключать их друг к другу, формируя всевозможные конфигурации серверов и фреймворков. Многие серверы выполняют рутинные задачи: анализируют URL или работают с HTTP.

Для разработки и тестирования компонентов создан экспериментальный фреймворк Tide.

Инструменты командной строки

Для инструментов командной строки нужно было объединить небольшие низкоуровневые библиотеки в абстракции более высокого уровня и отполировать некоторые существующие инструменты.

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

Например, если вы создаёте сложный инструмент вроде ripgrep или инструмент CLI поверх функциональности существующей библиотеки. Но Rust — отличный вариант для многих других инструментов.

И вы получаете абстракции высокого уровня, которых нет в других языках, таких как C и C++. Rust не требует среды выполнения и компилируется в один статический бинарник, что упрощает распространение программы.

Конечно, абстракции ещё более высокого уровня. Что ещё может улучшить Rust?

С абстракциями более высокого уровня быстро и легко собирается готовый CLI.

В отсутствии такой библиотеки в случае сбоя код CLI, вероятно, выдаст всю обратную трассировку. Примером такой абстракции является библиотека human panic. Можно добавить специальную обработку ошибок, но это сложно. Но она не очень интересна пользователям.

Пользователь увидит информативное сообщение, предлагающее сообщить о проблеме и загрузить файл дампа. С библиотекой human panic вывод автоматически направится в файл дампа ошибок.

Например, библиотека confy автоматизирует его настройку. Начало разработки CLI-инструментов тоже стало проще. Он спрашивает только две вещи:

  • Как называется приложение?
  • Какие параметры конфигурации вы хотите предоставить (которые вы определяете как структуру, которая может быть сериализована и десериализована)?

Всё остальное confy определит самостоятельно.

Что дальше?

Мы абстрагировали множество задач для CLI. Но можно абстрагировать ещё кое-что. Мы собираемся выпустить больше таких библиотек высокого уровня.

Когда вы пишете на каком-то языке, вы работаете с его инструментарием: начиная с редактора и продолжая другими инструментами на всех этапах разработки и поддержки.

Это означает, что эффективный язык зависит от эффективных инструментов.

Вот некоторые новые инструменты (и улучшения существующих) в Rust 2018.

Поддержка IDE

Конечно, производительность зависит от быстрого и плавного переноса кода из сознания разработчика на экран компьютера. Здесь решающее значение имеет поддержка IDE. Для этого нужны инструменты, которые могут «объяснить» IDE смысл кода Rust: например, подсказать осмысленные варианты для автодополнения строк.

С появлением Rust Language Server и IntelliJ Rust теперь многие IDE полностью поддерживают Rust. В Rust 2018 сообщество сосредоточилось на функциях, необходимых IDE.

Более быстрая компиляция

Повышение эффективности компилятора означает его ускорение. Это мы и сделали.

Теперь реализована инкрементальная компиляция: он компилирует только те части, которые изменились. Раньше, когда вы компилировали крейт Rust, компилятор заново компилировал каждый отдельный файл в крейте. Наряду с другими оптимизациями, это сделало компилятор Rust намного быстрее.

rustfmt

Эффективность также требует, чтобы мы никогда не спорили о правилах форматирования кода и не исправляли вручную чужие стили.

Rustfmt гарантирует, что весь код Rust соответствует одному стилю, подобно формату clang для C++ или Prettier для JavaScript. В этом помогает инструмент rustfmt: он автоматически переформатирует код в соответствии со стилем по умолчанию (по которому сообщество достигло консенсуса).

Clippy

Иногда приятно иметь рядом опытного консультанта, дающего советы о лучших практиках при написании кода. Это делает Clippy: он проверяет код во время его просмотра и подсказывает стандартные идиомы.

rustfix

Но если у вас старая кодовая база с устаревшими идиомы, то самостоятельно проверять и исправлять код может быть утомительно. Вы просто хотите, чтобы кто-то внёс исправления во всю кодовую базу.

Он одновременно и применяет правила из инструментов вроде Clippy, и обновляет старый код в соответствии с идиомами Rust 2018. В этих случаях rustfix автоматизирует процесс.

Изменения в экосистеме значительно повысили эффективность программирования. Но некоторые проблемы можно решить только изменениями в самом языке.

Все эти изменения являются частью Rust 2018. Как мы уже говорили во вступлении, большинство языковых изменений полностью совместимы с существующим кодом Rust. Но поскольку они ничего не ломают, то работают в любом коде Rust… даже в старом.

Затем посмотрим на небольшой список особенностей Rust 2018. Давайте посмотрим на важные функции, которые добавлены во все версии.

Новые функции для всех версий

Вот небольшой пример новых возможностей, которые есть (или будут) во всех версиях языка.

Более точная проверка заимствований

Одно большое преимущество Rust — это проверка заимствований. Она гарантирует, что код безопасен для памяти. Но это также довольно сложная функция для новичков в Rust.

Но есть и другая часть… Проверка заимствований иногда отклоняет код, который вроде бы должен работать с точки зрения программиста, который вполне понимает концепцию безопасности для памяти. Частично сложность заключается в изучении новых понятий.


Нельзя заимствовать переменную, потому что она уже заимствована

Такое происходит, потому что время жизни заимствования предположительно должно было распространяться до конца своей области — например, до конца функции, в которой находится переменная.

Это означало, что даже если переменная завершила работу со значением и больше не пытается получить доступ, другим переменным всё равно отказывают в доступе к этому значению до конца функции.

Теперь она видит, когда переменная фактически завершила использовать значение. Чтобы исправить ситуацию, мы сделали проверку умнее. После этого она не блокирует использование данных.

Скоро мы напишем подробнее на эту тему. Пока это доступно только в Rust 2018, но в ближайшее время функцию добавят во все остальные версии.

Процедурные макросы в стабильном Rust

Макросы в Rust были ещё до Rust 1.0. Но в Rust 2018 сделаны серьёзные улучшения, например, появились процедурные макросы. Они позволяют добавить в Rust собственный синтаксис.

Rust 2018 предлагает два вида процедурных макросов:

Макросы, подобные функциям

Макросы, подобные функциям, позволяют создавать объекты, которые выглядят как обычные вызовы функций, но фактически выполняются во время компиляции. Они берут один код и выдают другой, который компилятор затем вставляет в двоичный файл.

Макрос мог выполнять только оператор match. Они существовали раньше, но с ограниченем. У него не было доступа, чтобы посмотреть все токены во входящем коде.

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

Макросы, подобные атрибутам

Если вы знакомы с декораторами на языках вроде JavaScript, макросы атрибутов очень похожи. Они позволяют аннотировать фрагменты кода на Rust, которые следует предварительно обработать и превратить в нечто иное.

Когда вы помещаете его над структурой, компилятор принимает эту структуру (после того, как она проанализирована как список токенов) и обрабатывает её. Макрос derive делает именно это. В частности, добавляет базовую реализацию функций из трейта.

Более эргономичные заимствования в сопоставлениях

Тут незамысловатое изменение.

Раньше, если вы хотели что-то заимствовать и пытались выполнить сопоставление, нужно было добавить какой-то странный синтаксис:

Теперь вместо &Some(ref s) пишем просто Some(s).

Самая малая часть Rust 2018 — это функции, специфичные именно для этой версии. Вот небольшой набор изменений в Rust 2018.

Ключевые слова

В Rust 2018 добавлено несколько ключевых слов:

  • try
  • async/await

Эти функции ещё не полностью реализованы, но ключевые слова добавлены в Rust 1.31. Таким образом, в будущем не придётся вводить новые ключевые слова (что стало бы несовместимым изменением), когда мы реализуем эти функции.

Модульная система

Одна большая боль для новичков Rust — модульная система. И понятно почему. Трудно было понять, почему Rust выбирает тот или иной модуль. Чтобы исправить это, мы внесли некоторые изменения в механизм путей.

Но если переместить любой код в подмодуль, он больше не будет работать. Например, если вы импортировали крейт, то можете использовать его в пути на верхнем уровне.

// top level module
extern crate serde; // this works fine at the top level
impl serde::Serialize for MyType mod foo { // but it does *not* work in a sub-module impl serde::Serialize for OtherType { ... }
}

Другой пример — префикс ::, который используется и для корня крейта, и для внешнего крейта. Трудно понять, что перед нами.

Теперь если вы хотите обратиться к корневому крейту, то используете префикс crate::. Мы сделали это более явным. Это лишь одно из улучшений для понятности.

Но необязательно делать это вручную. Если вы хотите, чтобы текущий код использовал возможности Rust 2018, скорее всего, потребуется обновить код с учётом новых путей. Перед добавлением спецификатора версии в Cargo.toml просто запустите cargo fix — и rustfix внесёт необходимые изменения.

Всю информацию о новой версии языка содержит «Руководство по Rust 2018».

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть