Мы рады объявить третью редакцию языка Rust — Rust 2021, которая выйдет в октябре. Rust 2021 содержит несколько небольших изменений, которые, тем не менее, значительно улучшат удобство использования Rust.
Что такое Редакция?
Релиз Rust 1.0 установил "стабильность без застоя" как основное правило Rust. Начиная с релиза 1.0, это правило звучало так: выпустив функцию в стабильной версии, мы обязуемся поддерживать её во всех будущих выпусках.
Однако есть случаи, когда возможность вносить небольшие изменения в язык бывает полезной — даже если у них нет обратной совместимости. Самый очевидный пример — введение нового ключевого слова, которое делает недействительными переменные с тем же именем. Например, в первой версии Rust не было ключевых слов async
и await
. Внезапное изменение этих слов на ключевые слова в более поздних версиях привело бы к тому, что, например код let async = 1;
перестал работать.
Редакции — механизм, который мы используем для решения этой проблемы. Когда мы хотим выпустить функцию без обратной совместимости, мы делаем её частью новой редакции Rust. Редакции опциональны и должны прописываться явно, поэтому существующие пакеты не видят эти изменения, пока явно не перейдут на новую версию. Это означает, что даже последняя версия Rust по-прежнему не будет рассматривать async
как ключевое слово, если не будет выбрана версия 2018 или более поздняя. Этот выбор делается для каждого пакета как части Cargo.toml
. Новые пакеты, созданные cargo new
, всегда настроены на использование последней стабильной редакции.
Редакции не разделяют экосистему
Самое важное правило для редакций: пакеты одной редакции должны бесшовно взаимодействовать с пакетами другой. Это гарантирует, что миграция на новую редакцию будет частной для одного пакета и не затронет другие.
Требование функциональной совместимости подразумевает ограничения на типы изменений, которые мы можем внести в редакцию. В целом изменения, которые происходят в редакции, имеют тенденцию быть "поверхностными". Весь код Rust, независимо от редакции, компилируется в одно и то же внутреннее представление в компиляторе.
Миграция редакций проста и в значительной степени автоматизирована
Наша цель — упростить обновление пакетов. Когда мы выпускаем новую редакцию, мы также предоставляем инструменты для автоматизации миграции на неё. Они вносят незначительные изменения в код, необходимые для его совместимости. Например, при переходе на Rust 2018 они изменяют всё, что называлось async
, чтобы использовать эквивалентный синтаксис необработанного идентификатора: r#async
.
Автоматические миграции не всегда идеальны: в некоторых критических случаях требуются и ручные изменения. Инструменты изо всех сил пытаются избежать изменений семантики, которые могут повлиять на правильность или производительность кода.
В дополнение к инструментам мы также поддерживаем руководство по миграции, в котором описаны произошедшие в редакции изменения. В этом руководстве описаны все изменения, а также даны ссылки, где о них можно узнать больше. Кроме того, оно охватывает все важные детали, которые нужно иметь в виду при миграции. Таким образом, руководство служит как обзором редакции, так и справочником по быстрому устранению неполадок, которые могут возникнуть при использовании автоматизированных инструментов.
Какие изменения запланированы в Rust 2021?
За последние несколько месяцев рабочая группа Rust 2021 рассмотрела ряд предложений, что именно включить в новую редакцию. Мы рады объявить окончательный список изменений. Чтобы попасть в этот список, каждая функция должна соответствовать двум критериям. Во-первых, она должна быть одобрена соответствующей командой(ами) Rust. Во-вторых, для её реализации должно быть достаточно времени, чтобы мы могли быть уверены в том, что все этапы будут завершены вовремя.
Дополнения к прелюдии
Прелюдия стандартной библиотеки — это модуль, содержащий всё, что автоматически импортируется в каждый модуль. Она содержит часто используемые элементы, такие как Option
, Vec
, drop
и Clone
.
Компилятор Rust отдаёт приоритет любым элементам, импортированным вручную, перед элементами из прелюдии. Так мы уверены, что дополнения к прелюдии не нарушают какой-либо существующий код. Например, если у вас есть пакет или модуль с именем example
, содержащий pub struct Option;
, то use example::*;
заставит Option
однозначно ссылаться на example
не из стандартной библиотеки.
Однако добавление типажа к прелюдии может незаметно сломать существующий код. Вызов x.try_into()
с использованием MyTryInto
может стать неоднозначным и не скомпилироваться, если TryInto
из std
также импортирован, поскольку он предоставляет метод с тем же именем. По этой причине мы пока не добавили TryInto
в прелюдию — потому что много кода может сломаться.
В качестве решения Rust 2021 будет использовать новую прелюдию. Она идентична текущей, за исключением трёх новых дополнений:
Распознаватель функций Cargo по умолчанию
Начиная с Rust 1.51.0, Cargo поддерживает новый распознаватель функций который можно активировать с помощью resolver = "2"
в Cargo.toml
.
В Rust 2021 и всех дальнейших выпусках это будет значением по умолчанию. То есть запись edition = "2021"
в Cargo.toml
будет означать resolver = "2"
.
Новый распознаватель функций больше не объединяет все запрошенные функции для пакетов, от которых есть несколько зависимостей. Подробности смотрите в анонсе Rust 1.51.
IntoIterator для массивов
До Rust 1.53 только ссылки на массивы реализовывали IntoIterator
. Иными словами, вы могли выполнять итерацию по &[1, 2, 3]
и &mut [1, 2, 3]
, но не по [1, 2, 3]
напрямую.
for &e in &[1, 2, 3] {} // Ok :)
for e in [1, 2, 3] {} // Ошибка :(
Это давняя проблема, но решение не такое простое, как кажется. Просто добавление реализации типажа нарушит существующий код. Сейчас array.into_iter()
компилируется, потому что он неявно вызывает (&array).into_iter()
из-за особенностей синтаксиса вызова метода. Добавление реализации типажа изменит смысл.
Обычно мы классифицируем этот тип изменений (добавление реализации типажа) как "незначительный" и приемлемый. Но в этом случае оно может сломать слишком много кода.
Много раз предлагалось "реализовать IntoIterator
только для массивов в Rust 2021". Однако это попросту невозможно: у вас не может быть реализации типажа в одной редакции и не быть в другой, поскольку редакции могут быть смешанными.
Вместо этого мы решили добавить реализацию типажа во все редакции, начиная с Rust 1.53.0. В этом нам помог небольшой приём, чтобы избежать значительных изменений вплоть до Rust 2021. В коде Rust 2015 и 2018 компилятор по-прежнему будет преобразовывать array.into_iter()
в (&array).into_iter()
, будто реализации типажа не существует. Это относится только к синтаксису вызова метода .into_iter()
и не влияет на другие синтаксисы, такие как for e in [1, 2, 3]
, iter.zip([1, 2, 3])
. Они начнут работать во всех редакциях.
Нам жаль, что такое решение потребовало небольшой хитрости во избежание поломки, но мы очень довольны тем, как оно сводит разницу между редакциями к абсолютному минимуму. Поскольку этот хак присутствует только в старых версиях, в новой редакции нет никаких дополнительных сложностей.
Непересекающийся захват в замыканиях
Замыкания автоматически захватывают все, на что вы ссылаетесь из их тела. Например, || a + 1
автоматически захватывает ссылку на a
из окружающего контекста.
В настоящее время это относится ко всем структурам, даже если используется только одно поле. К примеру, || a.x + 1
заимствует ссылку на a
а не только на a.x
. В некоторых ситуациях это будет проблемой. Когда поле структуры уже заимствовано (изменяемо) или перемещено из неё, другие поля больше не могут использоваться в замыкании, так как замыкание будет пытаться захватить всю структуру, которая больше не доступна.
let a = SomeStruct::new();
drop(a.x); // удаляем одно поле структуры
println!("{}", a.y); // Окей: до сих пор используем только поле структуры
let c = || println!("{}", a.y); // Ошибка: попытка захвата всей структуры `a`
c();
Начиная с Rust 2021, замыкания будут захватывать только те поля, которые они используют. Приведённый выше пример отлично скомпилируется в Rust 2021.
Это поведение активируется только в новой редакции, поскольку оно может изменить порядок, в котором удаляются поля. Что же касается всех изменений редакций — для них доступна автоматическая миграция. Она обновит ваши замыкания, для которых это будет необходимо. Она может вставлять let _ = &a;
внутрь замыкания, чтобы захватить всю структуру, как раньше.
Согласованность макросов паники
panic!()
— один из самых известных макросов Rust. Однако в нем есть несколько тонких моментов, которые мы не можем просто взять и изменить — снова из-за обратной совместимости.
panic!("{}", 1); // Окей, паника с сообщением "1"
panic!("{}"); // Окей, паника без сообщения "{}"
Макрос panic!()
использует форматирование строки только тогда, когда вызывается с более чем одним аргументом. С одним аргументом он на него просто не смотрит.
let a = "{";
println!(a); // Ошибка: первый аргумент должен быть форматной строкой
panic!(a); // Хорошо: Макрос panic не обрабатывает аргумент
(Он даже принимает нестроковые аргументы, такие как panic!(123)
, что, впрочем, редко бывает полезно).
Особенно это будет проблемой после стабилизации неявных аргументов форматной строки. Эта функция сделает println!("hello {name}")
сокращением для println!("hello {}", name)
. Однако panic!("hello {name}")
не будет работать должным образом, поскольку panic!()
не обрабатывает единственный аргумент как строку.
Чтобы выйти из этой запутанной ситуации, в Rust 2021 есть более последовательный макрос — panic!()
. Новый panic!()
больше не принимает произвольные выражения в качестве единственного аргумента. Он — как и println!()
— всегда обрабатывает первый аргумент как строку. Поскольку panic!()
больше не будет принимать произвольные аргументы, panic_any()
будет единственным способом вызвать панику с чем-то кроме форматированной строки.
Кроме того, core::panic!()
и std::panic!()
будут идентичны в Rust 2021. В настоящее время между ними есть некоторые исторические различия, которые могут быть заметны при включении или выключении #![no_std]
.
Зарезервированный синтаксис
Чтобы освободить место для будущих изменений, мы решили зарезервировать синтаксис префиксных идентификаторов и литералов: prefix#identifier
, prefix"string"
, prefix'c'
и prefix#123
, где prefix
может быть любым идентификатором (за исключением тех, что уже имеют значение — например, b'...'
и r"..."
).
Это критическое изменение, поскольку в настоящее время макросы могут принимать hello"world"
, которое они будут видеть как два отдельных токена: hello
и "world"
. Хотя исправление (автоматическое) очень простое — просто вставьте пробел: hello "world"
.
Помимо превращения в ошибку токенизации, RFC пока не придаёт значения никакому префиксу. Присвоение значения конкретным префиксам остаётся на усмотрение будущих предложений, которые — благодаря резервированию этих префиксов сейчас — не повлекут за собой критических изменений.
Вот некоторые новые префиксы, которые вы увидите в будущем:
f""
как сокращение от форматной строки. Например,f"hello {name}"
как сокращение для эквивалентногоformat_args!()
.c""
илиz""
для С-строк с завершающим нулём.k#keyword
, позволяющее писать ключевые слова, которых ещё нет в текущей редакции. Например, хотяasync
и не является ключевым словом в версии 2015, этот префикс позволил бы нам принятьk#async
в редакции 2015, не дожидаясь выхода редакции 2018, чтобы зарезервироватьasync
в качестве ключевого слова.
Повышение двух предупреждений до серьёзных ошибок
Две существующие статические проверки станут серьёзными ошибками в Rust 2021. В старых версиях они останутся предупреждениями.
bare-trait-objects
— использование ключевого словаdyn
для идентификации типаж-объектов станет обязательным в Rust 2021.ellipsis-inclusive-range-patterns
—устаревший синтаксис ...
для включающих диапазонов больше не поддерживается в Rust 2021. Он был заменён на..=
, что согласуется с выражениями.
Шаблоны "или" в macro_rules
Начиная с Rust 1.53.0 паттерны расширены для поддержки |
, которые могут быть вложены в шаблоне где угодно. Это позволяет писать Some(1 | 2)
вместо Some(1) | Some(2)
. Поскольку раньше это было просто недопустимо, это не критическое изменение.
Однако это изменение также влияет на макросы macro_rules
. Такие макросы могут принимать шаблоны с использованием спецификатора фрагмента :pat
. В настоящее время :pat
не соответствует |
, поскольку до Rust 1.53 не все шаблоны (на всех вложенных уровнях) могли содержать |
. Макросы, которые принимают шаблоны типа A | B
, такие как matches!()
, используют что-то вроде $($_:pat)|+
. Поскольку мы не хотим нарушать макросы, которые уже существуют, мы не изменили значение :pat
в Rust 1.53.0, чтобы включить |
.
Вместо этого мы внесём это изменение как часть Rust 2021. В новой редакции спецификатор фрагмента :pat
будет соответствовать A | B
Поскольку бывают случаи, когда кто-то всё ещё желает сопоставить один вариант шаблона без |
, мы добавили спецификатор фрагмента :pat_param
для сохранения старого поведения. Название относится к его основному варианту использования: шаблон в параметре замыкания.
Что будет дальше?
Мы планируем объединить и полностью протестировать эти изменения к сентябрю, чтобы убедиться в том, что редакция 2021 года войдёт в Rust версии 1.56.0. Затем Rust 1.56.0 будет находиться в стадии бета-тестирования в течение шести недель, после чего он будет выпущен как стабильный релиз 21 октября.
Однако обратите внимание, что Rust — это проект, движимый волонтёрами. Мы ставим личное благополучие каждого, кто работает над Rust, выше любых сроков и ожиданий, которые мы могли бы установить. Это может означать задержку выпуска версии, если это будет необходимо, или отказ от функции, которая оказывается слишком сложной для завершения к релизу.
Тем не менее, мы идём по графику, и многие сложные проблемы уже решены. Спасибо всем, кто внёс свой вклад в Rust 2021!
Очередного анонса новой редакции можно ожидать в июле. На этом этапе мы ожидаем, что все изменения и автоматические миграции будут реализованы и готовы к общедоступному тестированию.
Мы будем публиковать более подробную информацию о процессе и об отклонённых предложениях в блоге "Inside Rust".
Если вы не можете ждать, то многие фичи уже доступны в Nightly, просто добавьте флаги -Zunstable-options --edition=2021
.
От переводчиков
С любыми вопросами по языку Rust вам смогут помочь в русскоязычном Телеграм-чате или же в аналогичном чате для новичковых вопросов. Если у вас есть вопросы по переводам или хотите помогать с ними, то обращайтесь в чат переводчиков.
Также можете поддержать нас на OpenCollective.
Данную статью совместными усилиями перевели fan-tom, blandger, Belanchuk, TelegaOvoshey и andreevlex.