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 ()
Последнее исправление: cetjs2 (всего исправлений: 5)

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

Я бы предложил Scala Native, но он еще более Preview, чем Kotlin.

Scala Native 0.3.7 released this on Mar 29

theLastOfCats
()
Ответ на: комментарий от Nexmean

C++ привычнее программистам, чем Rust, значит ли это что Rust не следует использовать?

Может быть. Но у Rust есть нужные возможности, отсутствующие в Си++. У Haskell vs Ocaml таких возможностей нет.

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

Знаешь, таких матерых отполированных язычков как ты хочешь всего 2 (два). И оба managed, чего ты не желаешь по религиозным что-ли мотивам. Так что пока укладывайся в криокамеру на десяток лет, единственно что можно тут посоветовать. Чем D не угодил я так и не вкурил. «Он мертвый кококо» это не аргумент.

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

Чем D не угодил я так и не вкурил.

Я его даже не упоминал. Утихомирите своих тараканов.

Но несмотря на то, что Ди мёртв, у него нет паттерн матчинга.

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

Может быть. Но у Rust есть нужные возможности, отсутствующие в Си++.

Это какие возможности, можно перечислить? Я в Rust не шарю, но после прочтения части доки он наводил только уныние

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

Но у Rust есть нужные возможности, отсутствующие в Си++.

Это какие возможности, можно перечислить?

Memory safety, ADT + pattern matching, трейты и impl, модули, Cargo.

в Rust не шарю, но после прочтения части доки он наводил только уныние

Тогда оно тебе не нужно.

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

C++ привычнее программистам, чем Rust, значит ли это что Rust не следует использовать?

Растоманы начинают что-то подозревать. Правда мазила честно пыталась сделать похоже на C++, но инопланетный гость Гра Йдо Нхоар им поднасрал таки.

anonymous
()
Ответ на: комментарий от Crocodoom

Ещё на уровне типов видно, что человек написал что-то, эквивалентное let y = ().

Если ты передашь y в какую-то функцию, то почти наверняка случится несоответствие типов и код не скомпилируется. В println!(«{:?}») можно передавать все, что имеет трейт Debug, поэтому и проходит. Но в реальности это очень редкая ситуация.

Вот этот код уже не скомпилится:

fn operate(a: u8, b: u8) -> u8 {
...
}

fn main() {
    let x = 1;
    let y = {
        1;
    };
    println!("{}", operate(x, y));
}
provaton ★★★★★
()
Ответ на: комментарий от tailgunner

У Haskell vs Ocaml таких возможностей нет.

Подробнее, пожалуйста!

Если что, то к «продакшему» haskell сейчас гораздо больше имеет отношения, чем ocaml. И между прочим, в haskell есть и строгие вычисления. Это для тех, кто не в курсе, а таких процентов 98%, или 99%, что влечет за собой как минусы, так и плюсы (да-да, «avoid success at all costs»).

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

У Haskell vs Ocaml таких возможностей нет.

Подробнее, пожалуйста!

Ээээ... я не могу рассказать подробнее. А если ты видишь у Haskell перед Ocaml такие же преимущества, как у Rust перед Си++ - расскажи об этом подробнее.

И между прочим, в haskell есть и строгие вычисления. Это для тех, кто не в курсе, а таких процентов 98%, или 99%

И об этом тоже подробнее, пожалуйста. Как же «Being lazy with class»?

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

ПМ подразумевался под функциональщиной.

У тебя странное представление о функциональщине. Если нужна развитая система типов, то выбор сужается до неприличия. Прямо скажем придется сидеть в петушином углу, зато с крутыми типами. Пиши на жабе/крестах и не выеживайся, петушиная возня не принесет счастья.

anonymous
()
Ответ на: комментарий от i36_zubov

В последнем PPS eao написал какую-то лютую дичь потому что его пример упрощается до

impl<'f> Context<'f> {
    fn new(foo: &'f Foo<'f>) -> Self {
        Context { foo }
    }   
}
mersinvald ★★★★★
() автор топика
Ответ на: комментарий от tailgunner

Окей, просмотрел, всё еще не уверен что это необходимо, но возможно я не прав и в том случае это оправдано.

Энивей, это настолько редкий случай, что даже упоминать всерьез смысла нет. Рядом можно написать какую-нибудь крайне редко нужную заковыристую дичь из любого языка и предъявить как претензию к синтаксису этого языка.

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

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

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

Если `Foo` инвариантно относительно `'f`, то не упрощается. Но да - редкий случай.

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

Ленивость по умолчанию, а строгие вычисления опциональны. Их надо руками проводить. Это и seq, и deepseq. Это и bang patterns, и прагма UNPACK. Многие типы, такие как ByteString (последовательность байт), имеют две версии - ленивую и строгую. Даже мапы (Map / IntMap) имеют такие версии. Я не говорю про UArray (массивы без боксинга) и соответствующую вещь Data.Vector.Unboxed из нового, но очень популярного пакета vector.

Если брать тип Double, то внутри тела функции он вполне может быть и строгим (как в лиспе), а вот на выходе из функции скорее всего будет thunk, да и то, если эта функция не инлайнится, что далеко не факт. Ну, а если нужен именно строгий вариант, то я бы посмотрел в сторону CDouble - аналог сишного типа double. Подозреваю, что он как раз таки строгий.

Ну, да. Обычно разумно использовать ленивые структуры данных. У них есть свои плюшки, как например, мемоизация и те же fix или mfix, что позволяют задавать рекурсивные вычисления. А в узких по производительности местах вполне можно использовать строгие вычисления, что и успешно делают на Haskell.

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

Про лисп я имел в виду, что там на выходе из функции будет тегированное значение, а в haskell - thunk. Такая аналогия.

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

Ленивость по умолчанию, а строгие вычисления опциональны. Их надо руками проводить.

Т.е. «если тебе надо, ты можешь сделать eager». Не совсем то, что надо прогерам оперденей - у них и так будет хватать когнитивного диссонанса.

И кстати, ты не ответил на вопрос.

Я ответил, как мог.

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

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

struct X {} // ;
f();
Добавление ; меняет поведение кода - объявление функции f() превращается в ее вызов.

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

Ну и замечательно

$ g++ -Wall foo.cpp 
foo.cpp: In function ‘int main()’:
foo.cpp:8:5: warning: this ‘for’ clause does not guard... [-Wmisleading-indentation]
     for (int i = 0; i < 10; ++i); // Oops
     ^~~
foo.cpp:9:9: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘for’
         printf("Hello, World!\n");
         ^~~~~~

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

В какой уже раз он одинарных кавычек пугается

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

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

Так переопределить кавычку надо иметь особый изгиб извилин

Кавычка имеет много значений, кроме как «символьный литерал». У тебя просто кругозор узкий.

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

Так переопределить кавычку надо иметь особый изгиб извилин.

You don't understand.

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

Не меняет оно никакое поведение, это ошибка компиляции.

В MSVC 2017 комилируется. Для clang & gcc можно слегка поменять пример

#include <iostream>
int v = 1;
int main()
{
    {
        enum X {A} // ;
        v = A;
    }
    std::cout << v << std::endl;
}

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

Так, Haskell определяют как «программируемую математику». Для средних программистов он не подходит. Что есть, то есть. Но я не вижу в этом ничего плохого.

Кстати, Rust тоже не так прост. Но для новых проектов я лично выбрал бы Rust вместо C++, если бы выбор стоял между ними.

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

Кстати, Rust тоже не так прост.

Так в том и дело, что Си тоже не прост. Rust «всего лишь» заставляет явно формулировать всё, что Си-прогер должен гарантировать и так :)

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

Ну, да, как бывший прогер на C++ могу сказать, что довольно нервозное дело было писать системный код на C++, который еще и работает в режиме 24/7 на серверах. В Rust я вижу, что компилятор может снизить уровень нервозности, если так можно выразиться. Я попробовал Rust, и мне очень понравились его сообщения об ошибках. Часто страхует (конечно, не всегда).

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

Кавычка имеет много значений, кроме как «символьный литерал». У тебя просто кругозор узкий.

Кавычка в контексте программирования имеет определенное значение по историческим причинам. Кругозор свой прибереги для ЧГК.

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

Да, перл упорот, но даже Ларри Уоллу не пришло в голову сделать кавычку префиксом.

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

Кавычка в контексте программирования имеет определенное значение по историческим причинам.

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

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

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

i36_zubov
()
Ответ на: комментарий от dave

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

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