LINUX.ORG.RU

Rust 1.27

 


3

10

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

Основные изменения:

  • SIMD — наиболее значимое и ожидаемое нововведение: стабильная версия Rust обзавелась базовой поддержкой SIMD.
    Для примера использования рассмотрим следующий сниппет:
    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 инструкции автоматически.

    Стабильный Rust уже давно использует возможности автовекторизации LLVM, но, к сожалению, компилятор может использовать подобные оптимизации не всегда. В Rust 1.27.0 добавлен модуль std::arch, включающий базовую поддержку использования инструкций SIMD напрямую из кода на Rust. Кроме того, добавлены соответствующие директивы условной компиляции:

    #[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 позволяют выбрать правильную версию функции в зависимости от целевой архитектуры во время компиляции. Также мы можем это сделать в рантайме:
    fn foo() {
        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
        {
            if is_x86_feature_detected!("avx2") {
                return unsafe { foo_avx2() };
            }
        }
    
        foo_fallback();
    }
    

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

    Без SIMD

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

  • dyn Trait

    Изначальный синтаксис трейт-объектов в Rust — одна из тех вещей, о введении которых мы жалеем: для некоторого трейта Foo, его трейт-объект будет выглядеть так: Box<Foo>

    И если Foo является структурой, синтаксис аллокации структуры в «куче» будет выглядеть точно так же.

    Когда язык разрабатывался, казалось что сходство здесь уместно, но опыт показал, что это только путает людей.

    То же самое справедливо для случая impl SomeTrait for SomeOtherTrait, что является корректным синтаксисом, но интуитивно понимается как реализация трейта SomeTrait для всех типов, реализующих SomeOtherTrait, но на самом деле это записывается как impl<T> SomeTrait for T where T: SomeOtherTrait, в то время как первый вариант является реализацией трейта для трейт-объекта.

    Кроме того, появление impl Trait в противовес Trait ввело некую неконсистентность при обучении языку.

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

    // old => new
    Box<Foo> => Box<dyn Foo>
    &Foo => &dyn Foo
    &mut Foo => &mut dyn Foo
    

    То же самое применимо к другим типам указателей:
    Arc<Foo> теперь объявляется как Arc<dyn Foo>.

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

  • #[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, теперь компилятор предупредит в случае, если их результат останется неиспользованным, что поможет заметить и предотвратить случайный вызов затратных операций.

Стабилизация стандартной библиотеки

Новые возможности Cargo

В текущем релизе в Cargo включены два маленьких нововведения:

Во-первых, Cargo теперь принимает флаг --target-dir.

Во-вторых, Cargo теперь будет пытаться автоматически найти тесты, примеры и исполняемые файлы (прим. бинарные подпроекты в библиотечных крейтах) в проекте, но явная конфигурация всё же будет требоваться в некоторых случаях.

Для помощи в конфигурации мы добавили несколько ключевых слов в Cargo.toml.

Обновить Rust можно с помощью команды:

curl https://sh.rustup.rs -sSf | sh # если у вас еще не установлен rustup
rustup update stable

>>> Подробности

Ответ на: комментарий от tailgunner

Да болт уже на этих жрущищ баб, простое очевидное решение - не кормить, раз возникают подобные проблемы.
Лучше про раст расскажи, свое виденье.
Он действительно вкусен? На первый взгляд похоже на попытку и рыбку съесть, и жопу не ободрать. Например, gc не нужен, но тут же начинаются какие-то пляски на руках вокруг времени жизни с не очень красивыми и внятными выражениями. Концепцию конечно впитать можно, но это действительно оправдано и перспективно? Какие ниши он реально возможно займет?

madcore ★★★★★ ()
Ответ на: комментарий от tailgunner

sleep, я думаю, тоже, да и вообще отработка sleep за нулевое время делает бессмысленным его добавление.

Скорее как раз тут. Смотря через что и как был реализован слип - 1000 могло быть ок, а 150 - 0. Тем паче
thread::sleep_ms(150) - сейчас одичало
thread::sleep(Duration::from_millis(150)) - на тот момент могло не работать, что логично

madcore ★★★★★ ()
Последнее исправление: madcore (всего исправлений: 1)
Ответ на: комментарий от madcore

Я не писал и не пишу на Rust ничего серьезного, просто тыкал его для себя, поэтому всё нижесказанное может быть не соотетствовать реальности. По-моему, Rust - лучший на сегодня язык системного программирования (под системным программированием понимаются задачи, где необходимы нулевой или минимальный рантайм с нулевыми накладными расходами). Но порог входа высок, среднему сишнику не осилить (среднему крестовику, пожалуй, будет даже труднее). Взлетит ли язык - зависит от людей. Я лично испытываю умеренный оптимизм.

Например, gc не нужен

«GC не нужен» - это неправильное понимание. Правильное - это «как нам обеспечить memory safety в условиях, где GC неприменим?».

но тут же начинаются какие-то пляски на руках вокруг времени жизни с не очень красивыми и внятными выражениями.

Например? И что предлагается в качестве альтернативы - Си-стайл, где время жизни не описывается (и не проверяется) вообще никак?

Концепцию конечно впитать можно, но это действительно оправдано и перспективно?

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

Но возможна другая неприятность - после знакомства с Rust остальные распространенные языки будут казаться тебе любительскими поделками (это довод против его изучения вообще).

tailgunner ★★★★★ ()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от madcore

thread::sleep(Duration::from_millis(150)) - на тот момент могло не работать, что логично

Если немного подумать, то логики в этом нет, а подход «оппонент дурак» заведомо лузерский.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Тебя не смущает
1. sleep_ms - деприкейтед
2. sleep вероятно до какого-то момента не работало с ms-квантатми

а подход «оппонент дурак» заведомо лузерский.

?

madcore ★★★★★ ()
Ответ на: комментарий от madcore

а подход «оппонент дурак» заведомо лузерский.

?

Предполагать, sleep работает неправильно - это подход «оппонент дурак». Предполагать, что в код добавлен sleep, не имеющий эффекта - то же самое.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Например? И что предлагается в качестве альтернативы - Си-стайл, где время жизни не описывается (и не проверяется) вообще никак?

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

зарабатывать программировыанием на Rust, то влезать пока рано.

Да я и не готов пока за такое платить..

Но возможна другая неприятность - после знакомства с Rust остальные распространенные языки будут казаться тебе любительскими поделками (это довод против его изучения вообще).

Oh... Такое относится скорее к академическим поделкам, а не к неоформленному нечто.

madcore ★★★★★ ()
Ответ на: комментарий от madcore

ансейв дыр понаделали

Чушь.

зарабатывать программировыанием на Rust, то влезать пока рано.

Да я и не готов пока за такое платить..

Не думал, что ты нанимаешь программистов. На эту тему я вообще не имею мнения.

Такое относится скорее к академическим поделкам, а не к неоформленному нечто.

ЯННП.

tailgunner ★★★★★ ()
Ответ на: комментарий от madcore

Недовывод типов зачем вообще...

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

red75prim ★★ ()
Ответ на: комментарий от MyTrooName

Там самое годное это Iterator::step_by и Path::ancestors. Остальное минорщина, да.

RazrFalcon ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.