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

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

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

Спасибо.

Только пишут, что nphysics это игровой движок для «твёрдого» тела,
но может и из него что-то можно использовать по мелочи. А сам он nalgebra использует.

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

Ну да, я просто добавил на случай мало ли область схожая.

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

Посмотрел, ничего не понял и написал свой костыль чем то похожий на VM

tailgunner мне Cretonne показался каким то тяжелым для простенького языка

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

мне Cretonne показался каким то тяжелым для простенького языка

Ну, ты хотел JIT - вот тебе настоящий JIT :). Он не самый тяжелый, кстати.

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

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

mersinvald ★★★★ ()

Примеры кода замечательные! Я ещё не знаю этот язык, но он уже мне как родной. Обязательно поковыряю. На пенсии.

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

за размещение ссылки на ютуб вместо пары слов текста хочется убивать

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

конечно же я кликну. вот только отправлю это сообщение и кликну.

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

Да ладно медиа. Стикеры хотя бы запилили

makoven ★★★★★ ()

Почему пример из книги работает неверно?

use std::thread;
use std::time::Duration;
use std::sync::{Mutex, Arc};

struct Philosopher {
    name: String,
    left: usize,
    right: usize,
}

impl Philosopher {
    fn new(name: &str, left: usize, right: usize) -> Philosopher {
        Philosopher {
            name: name.to_string(),
            left: left,
            right: right,
        }
    }

    fn eat(&self, table: &Table) {
        let _left = table.forks[self.left].lock().unwrap();
        thread::sleep(Duration::from_millis(150));
        let _right = table.forks[self.right].lock().unwrap();

        println!("{} начала есть.", self.name);

        thread::sleep(Duration::from_millis(1000));

        println!("{} закончила есть.", self.name);
    }
}

struct Table {
    forks: Vec<Mutex<()>>,
}

fn main() {
    let table = Arc::new(Table { forks: vec![
        Mutex::new(()),
        Mutex::new(()),
        Mutex::new(()),
        Mutex::new(()),
        Mutex::new(()),
    ]});

    let philosophers = vec![
        Philosopher::new("Джудит Батлер", 0, 1),
        Philosopher::new("Рая Дунаевская", 1, 2),
        Philosopher::new("Зарубина Наталья", 2, 3),
        Philosopher::new("Эмма Гольдман", 3, 4),
        Philosopher::new("Анна Шмидт", 0, 4),
    ];

    let handles: Vec<_> = philosophers.into_iter().map(|p| {
        let table = table.clone();

        thread::spawn(move || {
            p.eat(&table);
        })
    }).collect();

    for h in handles {
        h.join().unwrap();
    }
}

Бабы едят по очереди.

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

Потому что у тебя потоки запускаются по очереди?

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

Потому что у тебя

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

потоки запускаются по очереди?

Там ведь в каждом потоке слипы есть, т.е. схватить вилку должны успеть больше одной.

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

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

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

Но в книжке-то иначе

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

Рая Дунаевская начала есть.
Эмма Гольдман начала есть.
Эмма Гольдман закончила есть.
Рая Дунаевская закончила есть.
Джудит Батлер начала есть.
Зарубина Наталья начала есть.
Джудит Батлер закончила есть.
Анна Шмидт начала есть.
Зарубина Наталья закончила есть.
Анна Шмидт закончила есть.
Поздравляем! Вы реализовали классическую задачу параллелизма на языке Rust.

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

Но в книжке-то иначе

Не в книжке, а на машине автора.

И да, там одна получается левша.

Это способ избежать полной блокировки.

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

В англицкой 1.2.0 нашел рабочий(как описано) пример, отличается отсутствием слипа(150).
Сейчас в оригинальном 2018-edition этот пример вообще выпилен...

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

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

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

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

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

Ты не понял.

Так уж ты объяснял.

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

Процитируй, где ты увидел, что именно одновременное «жрание» двоими является решением.

Из английской 1.2.0 - жрут две.

То есть теперь ты знаешь ответ на свой вопрос www.linux.org.ru/news/mozilla/14299923?cid=14414523?

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

Уже ж.

И вот:
With this, our program works! Only two philosophers can eat at any one time, and so you’ll get some output like this:

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

With this, our program works! Only two philosophers can eat at any one time, and so you’ll get some output like this:

Во-первых, это не тот текст, на который ты сослался изначально; во-вторых, почему ты решил, что это является необходимым свойством решения?

Просто для протокола: задача об обедающих философах - это классическая задача для демонстрации алгоритмов избежания полной взаимной блокировки.

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

Во-первых, это не тот текст, на который ты сослался изначально;

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

во-вторых, почему ты решил, что это является необходимым свойством решения?

А зачем вообще такая постановка задачи, если можно просто поставить всех в очередь?

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

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

А может, переводчик решил сделать изучение более интересным - чтобы читатели искали «ошибки» и за счет этого лучше понимали алгоритмы %)

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

Напиши свои претензии сюда, если не лень регистрироваться в Gitter. Хотя сейчас вроде бы они вторую редакцию Растбуки переводят. В принципе, в ruRust и для других вопросов найдутся подходящие чаты-комнаты.

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

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

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

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

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

Проблема тут не в переводе, а в актуальности данных.
А в текущем оригинале этого кода вообще нет.

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

А в текущем оригинале этого кода вообще нет.

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

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

Only two

это означает, что не больше двух

output like this

это означает, что может быть и другой выхлоп

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

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

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

Только если не успеть схватить вилку за 1000ms...

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

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

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

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

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

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

Пойнт в том, что и в английской версии может (когда-нибудь, очень редко) получаться «очередь».

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

Нет. Пока ты не поймешь, что оба варианта теоретически равноценны, не программируй ничего многозадачного.

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

Там разница только в наличии задержки между обретением левой и правой щетки. Понятное дело, что, например, подобная задержка может возникнуть и по внешним причинам.

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

Какой еще щетки? От зубного просто пришел.

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

Правильно. Поэтому оба варианта могут демонстрировать одинаковое поведение.

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

Для обоих вариантов, чтобы им поменяться поведением, нужны несколько аномальные сложноуловимые условия(для неэкзотических систем и творящегося на них), за рамками «примера тредов» из книжки с описанным поведением и выхлопом(две жрут, другие курят). Не думаю, что наиболее вероятная очередь - то, чего хотели продемонстрировать «русским» примером. Либо в той версии раста треды работали иначе, либо thread::sleep(Duration::from_millis(150)) отрабатывало за 0.
В конце концов, из нынешней книжки эту задачу-пример почему-то выпилили.

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

Либо в той версии раста треды работали иначе, либо thread::sleep(Duration::from_millis(150)) отрабатывало за 0.

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

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