Хабрахабр

[Перевод] Выпуск Rust 1.27

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

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

$ rustup update stable

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

27. Также мы хотим обратить ваше внимание вот на что: перед выпуском версии 1. 26. 0 мы обнаружили ошибку в улучшении сопоставлений match, введенном в версии 1. Поскольку она была обнаружена очень поздно, уже в процессе выпуска данной версии, хотя присутствует с версии 1. 0, которая может привести к некорретному поведению. 0, мы решили не нарушать заведенный порядок и подготовить исправленную версию 1. 26. 1, которая выйдет в ближайшее время. 27. 26. И дополнительно, если потребуется, версию 1. Подробности вы сможете узнать из соответствующих примечаний к выпуску. 3.

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

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

SIMD

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

pub fn foo(a: &[u8], b: &[u8], c: &mut [u8])
}

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

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

#[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 — своя.

fn foo() { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { if is_x86_feature_detected!("avx2") { return unsafe { foo_avx2() }; } } foo_fallback();
}

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

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

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, вам потребуется изменить его так:

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();

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

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

dyn Trait

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

Box<Foo>

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

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

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

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

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

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

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

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

#[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

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

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

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

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

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

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

Спасибо!

От переводчика: выражаю отельную благодарность участникам сообщества ruRust и лично ozkriff за помощь с переводом и вычиткой

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

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

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

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

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