21 Июня 2018 • оригинал: The Rust Core Team • перевод: XX • новости • поддержите на Patreon

Команда разработчиков Rust рада сообщить о выпуске новой версии Rust: 1.27.0. Rust — это системный язык программирования, нацеленный на безопасность, скорость и параллельное выполнение кода.

Если у вас установлена предыдущая версия Rust с помощью rustup, то для обновления Rust до версии 1.27.0 вам достаточно выполнить:

1 $ rustup update stable

Если у вас ещё не установлен rustup, вы можете установить его с соответствующей страницы нашего веб-сайта. С подробными примечаниями к выпуску Rust 1.27.0 можно ознакомиться на GitHub.

Также мы хотим обратить ваше внимание вот на что: перед выпуском версии 1.27.0 мы обнаружили ошибку в улучшении сопоставлений match , введённом в версии 1.26.0, которая может привести к некорректному поведению. Поскольку она была обнаружена очень поздно, уже в процессе выпуска данной версии, хотя присутствует с версии 1.26.0, мы решили не нарушать заведённый порядок и подготовить исправленную версию 1.27.1, которая выйдет в ближайшее время. И дополнительно, если потребуется, версию 1.26.3. Подробности вы сможете узнать из соответствующих примечаний к выпуску.

Что вошло в стабильную версию 1.27.0

В этом выпуске выходят два больших и долгожданных улучшения языка. Но сначала небольшой комментарий относительно документации: во всех книгах в библиотечке Rust теперь доступен поиск! Например, так можно найти «заимствование» («borrow») в книге «Язык программирования Rust» Надеемся, это облегчит поиск нужной вам информации. Кроме того, появилась новая Книга о rustc . В этой книге объясняется, как напрямую использовать rustc , а также как получить другую полезную информацию, такую как список всех статических проверок.

SIMD

Итак, теперь о важном: отныне в Rust доступны базовые возможности использования SIMD! SIMD означает «одиночный поток команд, множественный поток данных» (single instruction, multiple data). Рассмотрим функцию:

1 2 3 4 5 pub fn foo ( a : & [ u8 ], b : & [ u8 ], c : & mut [ u8 ]) { for (( a , b ), c ) in a .iter () .zip ( b ) .zip ( c ) { * c = * a + * b ; } }

Здесь мы берём два целочисленных среза, суммируем их элементы и помещаем результат в третий срез. Приведённый выше код демонстрирует самый простой способ сделать это: нужно пройтись по всему набору элементов, сложить их вместе и сохранить результат. Однако, компиляторы зачастую находят решение получше. LLVM часто «автоматически векторизует» подобный код, где такая затейливая формулировка означает просто «использует SIMD». Представьте, что срезы a и b имеют длину в 16 элементов оба. Каждый элемент — это u8 , а значит срезы будут содержать по 128 бит данных каждый. Используя SIMD, мы можем разместить оба среза a и b в 128-битных регистрах, сложить их вместе одной инструкцией и затем скопировать результирующие 128 бит в c . Это будет работать намного быстрее!

Несмотря на то, что стабильная версия Rust всегда была в состоянии использовать преимущества автоматической векторизации, иногда компилятор просто недостаточно умён, чтобы понять, что можно её применить в данном случае. Кроме того, не все CPU поддерживают такие возможности. Поэтому LLVM не может использовать их всегда, так как ваша программа может работать на самых разных аппаратных платформах. Поэтому в Rust 1.27, с добавлением модуля std::arch , стало возможно использовать эти виды инструкций напрямую, то есть теперь мы не обязаны полагаться только на интеллектуальную компиляцию. Дополнительно у нас появилась возможность выбирать конкретную реализацию в зависимости от различных критериев. Например:

1 2 3 4 5 6 7 8 9 10 11 12 # [ cfg ( all ( any ( target_arch = "x86" , target_arch = "x86_64" ), target_feature = "avx2" ))] fn foo () { #[cfg(target_arch = "x86" )] use std :: arch :: x86 :: _mm256_add_epi64 ; #[cfg(target_arch = "x86_64" )] use std :: arch :: x86_64 :: _mm256_add_epi64 ; unsafe { _mm256_add_epi64 ( ... ); } }

Здесь мы используем флаги cfg для выбора правильной версии кода в зависимости от целевой платформы: на x86 будет использоваться своя версия, а на x86_64 — своя. Мы также можем выбирать и во время выполнения:

1 2 3 4 5 6 7 8 9 10 fn foo () { #[cfg(any(target_arch = "x86" , target_arch = "x86_64" ))] { if is_x86_feature_detected! ( "avx2" ) { return unsafe { foo_avx2 () }; } } foo_fallback (); }

Здесь у нас имеется две версии функции: одна использует AVX2 — специфический вид SIMD, который позволяет выполнять 256-битные операции. Макрос is_x86_feature_detected! сгенерирует код, который проверит, поддерживает ли процессор AVX2, и если да, то будет вызвана функция foo_avx2 . Если нет, то мы прибегнем к реализации без AVX, foo_fallback . Значит наш код будет работать очень быстро на процессорах, поддерживающих AVX2, но также будет работать и на остальных процессорах, хотя и медленнее.

Все это выглядит слегка низкоуровневым и неудобным — да, так и есть! std::arch — это именно примитивы для такого рода вещей. Мы надеемся, что в будущем мы все-таки стабилизируем модуль std::simd с высокоуровневыми возможностями. Но появление базовых возможностей работы с SIMD позволяет теперь экспериментировать с высокоуровневой поддержкой различным библиотекам. Например, посмотрите пакет faster. Вот фрагмент кода без SIMD:

1 2 3 4 5 let lots_of_3s = ( & [ - 123.456f32 ; 128 ][ .. ]) .iter () .map (| v | { 9.0 * v .abs () .sqrt () .sqrt () .recip () .ceil () .sqrt () - 4.0 - 2.0 }) .collect :: < Vec < f32 >> ();

Для использования SIMD в этом коде с помощью faster , вам потребуется изменить его так:

1 2 3 4 5 let lots_of_3s = ( & [ - 123.456f32 ; 128 ][ .. ]) .simd_iter () .simd_map ( f32s ( 0.0 ), | v | { f32s ( 9.0 ) * v .abs () .sqrt () .rsqrt () .ceil () .sqrt () - f32s ( 4.0 ) - f32s ( 2.0 ) }) .scalar_collect ();

Он выглядит почти таким же: simd_iter вместо iter , simd_map вместо map , f32s(2.0) вместо 2.0 . Но в итоге вы получаете SIMD-версию вашего кода.

Помимо этого, вы можете никогда не писать такое сами, но, как всегда, это могут делать библиотеки, от которых вы зависите. Например, в пакет regex уже добавили поддержку, и его новая версия будет иметь SIMD-ускорение без необходимости вам вообще что-либо делать!

dyn Trait

В конечном итоге мы пожалели о выбранном изначально синтаксисе типажей-объектов в Rust. Как вы помните, для типажа Foo можно так определить типаж-объект:

1 Box < Foo >

Однако, если Foo — была бы структура, это означало бы просто размещение структуры внутри Box<T> . При разработке языка мы думали, что такое сходство будет хорошей идеей, но опыт показал, что это приводит к путанице. И дело не только в Box<Trait> : impl SomeTrait for SomeOtherTrait также является формально корректным синтаксисом, но вам почти всегда требуется написать impl<T> SomeTrait for T where T: SomeOtherTrait вместо этого. То же самое и с impl SomeTrait , который выглядит так, будто добавляет методы или возможную реализацию по умолчанию в типаж, но на самом деле он добавляет собственные методы в типаж-объект. Наконец, по сравнению с недавно добавленным синтаксисом impl Trait , синтаксис Trait выглядит короче и предпочтительней к использованию, но на самом деле это не всегда верно.

Поэтому в Rust 1.27 мы стабилизировали новый синтаксис dyn Trait . Типажи-объекты теперь выглядят так:

1 2 3 4 // было => стало Box < Foo > => Box < dyn Foo > & Foo => & dyn Foo & mut Foo => & mut dyn Foo

Аналогично и для других типов-указателей: Arc<Foo> теперь Arc<dyn Foo> и т. д. Из-за требования обратной совместимости мы не можем удалить старый синтаксис, но мы добавили статическую проверку bare-trait-object , которая по умолчанию разрешает старый синтаксис. Если вы хотите запретить его, то вы можете активировать данную проверку. Мы подумали, что с проверкой, включённой по умолчанию, сейчас будет выводиться слишком много предупреждений.

Кстати, мы работаем над инструментом под названием rustfix , который сможет автоматически обновлять ваш код до более новых идиом. Он будет использовать подобные статические проверки для этого. Следите за сообщениями о rustfix в будущих анонсах.

#[must_use] для функций

В заключении, было расширено действие атрибута #[must_use] : теперь он может использоваться для функций.

Раньше он применялся только к типам, таким как Result <T, E> . Но теперь вы можете делать так:

1 2 3 4 5 6 7 8 9 10 # [ must_use ] fn double ( x : i32 ) -> i32 { 2 * x } fn main () { double ( 4 ); // warning: unused return value of `double` which must be used let _ = double ( 4 ); // (no warning) }

С этим атрибутом мы также слегка улучшили стандартную библиотеку: Clone::clone , Iterator::collect и ToOwned::to_owned будут выдавать предупреждения, если вы не используете их возвращаемые значения, что поможет вам заметить дорогостоящие операции, результат которых вы случайно игнорируете.

Подробности смотрите в примечаниях к выпуску.

Стабилизация библиотек

В этом выпуске были стабилизированы следующие новые API:

Подробности смотрите в примечаниях к выпуску.

Улучшения в Cargo

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

Дополнительно, доработан подход Cargo к тому, как обрабатывать цели. Cargo пытается обнаружить тесты, примеры и исполняемые файлы в рамках вашего проекта. Однако иногда требуется явная конфигурация. Но в первоначальной реализации это сделать было проблематично. Скажем, у вас есть два примера, и Cargo их оба обнаруживает. Вы хотите сконфигурировать один из них, для чего добавляете [[example]] в Cargo.toml , чтобы указать параметры примера. В настоящее время Cargo увидит, что вы определили пример явно, и поэтому не будет пытаться делать автоматическое определение других. Это слегка огорчает.

Поэтому мы добавили несколько ‘auto’-ключей в Cargo.toml . Мы не можем исправить такое поведение без возможной поломки проектов, которые по неосторожности на него полагались. Поэтому если вы хотите сконфигурировать некоторые цели, но не все, вы можете установить ключ autoexamples в true в секции [package] .

Подробности смотрите в примечаниях к выпуску.

Разработчики 1.27.0

Множество людей участвовало в разработке Rust 1.27. Мы не смогли бы завершить работу без участия каждого из вас.

Спасибо!