LINUX.ORG.RU

Rust 1.13

 


4

10

Представлен релиз Rust 1.13 — системного языка программирования, нацеленного на безопасную работу с памятью, скорость и параллельное выполнение кода. В этот релиз вошли 1448 патчей.

Этот сезон оказался очень плодотворным для Rust. Проведены конференции RustConf, RustFest и Rust Belt Rust. Обсуждено будущее языка, разработан план на 2017 год и созданы новые инструменты.

Новое в 1.13

Выпуск 1.13 содержит несколько расширений языка, включая долгожданный оператор ?, оптимизацию времени компиляции, новые возможности cargo и стандартной библиотеки, а также множество мелких улучшений в документации и исправления ошибок.

Cargo в этом релизе содержит важные обновления безопасности, связанные с зависимостями от curl и OpenSSL, для которых также недавно были опубликованы обновления безопасности. Подробную информацию можно найти в соответствующих источниках для curl 7.51.0 и OpenSSL 1.0.2j.

Оператор ?

Добавлен новый оператор ?, делающий обработку ошибок приятнее за счёт уменьшения визуального шума. В качестве иллюстрации представим следующий код, который считывает некоторые данные из файла:

fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("username.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

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

С оператором ?, вышестоящий код выглядит следующим образом:

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("username.txt")?;
    let mut s = String::new();

    f.read_to_string(&mut s)?;

    Ok(s)
}

Оператор ? заменяет весь код обработки ошибок, написанный при помощи оператора match ранее. Иными словами, ? применяется к значению Result, и если оно равно Ok, разворачивает его и отдаёт вложенное значение; если это Err, то происходит возврат из функции, в которой вы находитесь.

Более опытные пользователи могут заметить, что этот оператор делает то же самое, что и макрос try!, который доступен начиная с Rust 1.0. И будут правы, в самом деле, это то же самое. До 1.13 read_username_from_file можно было бы написать следующим образом:

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = try!(File::open("username.txt"));
    let mut s = String::new();

    try!(f.read_to_string(&mut s));

    Ok(s)
}

Так зачем надо было расширять язык, если до этого уже был такой макрос? Есть несколько причин. Во-первых, try! доказал своё огромное значение и часто используется в идеоматичном Rust. Он используется так часто, что было принято решение о создании собственного «подслащенного» синтаксиса для него. Такой вид эволюции — одно из преимуществ мощной системы макросов: расширения к синтаксису языка можно добавлять через прототипирование без внесения изменений в сам язык и особо полезные макросы могут указать на недостающие возможности языка. Эволюция try! в ? — яркий пример этого.

Другая причина — восприятие нескольких последовательных вызовов try!:

try!(try!(try!(foo()).bar()).baz())
против
foo()?.bar()?.baz()?

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

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

Более подробно об операторе ? можно прочитать в RFC 243.

Улучшение производительности

В последнее время очень много внимания заострено на производительности компилятора. Mark Simulacrum и Nick Cameron произвели улучшения http://perf.rust-lang.org/, инструмента для отслеживания производительности компилятора, на котором периодически запускается набор rustc-benchmarks на выделенном оборудовании. Инструмент записывает результаты каждого прохода компилятора и позволяет находить и отслеживать код, приведший к регрессии. Например, при помощи этого инструмента можно посмотреть график производительности за весь цикл разработки релиза 1.13, где можно увидеть заметное сокращение времени работы компилятора, отдельно представленное на соответствующей странице со статистикой.

Большое улучшение на графике от 1 сентября связано с оптимизацией от Niko по кешированию нормализованных проекций во время преобразования. То есть во время генерации промежуточного представления LLVM компилятор больше не пересчитывает каждый раз конкретные экземпляры связанных типов, когда они необходимы, а использует ранее вычисленные значения. Несмотря на то, что данная оптимизация не влияет на всю кодовую базу, для некоторого кода с определенным шаблоном, например, futures-rs, ускорение сборки в режиме отладки достигает 40%.

Другая оптимизация от Michael Woerister уменьшает время компиляции библиотек, экспортирующих множество встраиваемых функций. Когда функция помечена как «#[inline]», в дополнение к преобразованию этой функции в текущей библиотеке компилятор сохраняет её представление MIR и преобразует функцию в представление LLVM в каждой библиотеке, которая вызывает её. Оптимизация, сделанная Michael Woerister, позволяет компилятору избегать предварительных преобразований кода встраиваемых функций в библиотеках, в которых они определены, до их непосредственного прямого вызова. Таким образом, компилятор избавляется от необходимости выполнения лишних шагов по преобразованию функции в промежуточное представление LLVM, оптимизации LLVM и преобразования функции в машинный код.

В некоторых случаях это приводит к впечатляющим результатам. Например, время сборки библиотеки ndarray уменьшилось на 50%, а библиотека winapi 0.3 (ещё не опубликована) полностью избавилась от шага генерации машинного кода.

Но это ещё не всё: Nick Nethercote обратил своё внимание на производительность компилятора, сконцентрировавшись на профилировании и микрооптимизациях. Этот релиз включает в себя некоторые плоды его работ, ещё больше ожидается в 1.14.

Другие заметные изменения

Макросы теперь можно использовать на позиции типов (RFC 873), а атрибуты могут быть применены к операторам (RFC 16):

// Use a macro to name a type
macro_rules! Tuple {
    { $A:ty,$B:ty } => { ($A, $B) }
}

let x: Tuple!(i32, i32) = (1, 2);

// Apply a lint attribute to a single statement
#[allow(uppercase_variable)]
let BAD_STYLE = List::new();

Были удалены встраиваемые флаги сброса. Раньше при условном перемещении компилятор встраивал «флаг сброса» в структуру (увеличивая его размер), чтобы отслеживать, когда надо его сбросить. Из-за этого некоторые структуры занимали больше места, что мешало передаче типов с деструкторами поверх FFI. Благодаря тому, что в версии 1.12 добавлен MIR, появилась основа для многих улучшений, включая удаление встраиваемых флагов сброса. Теперь флаги сброса хранятся в дополнительном слоте в стеке тех функций, которым они нужны.

Релиз 1.13 содержит серьёзную ошибку в генерации кода для ARM с аппаратной реализацией чисел с плавающей точкой. Поскольку 1.13 содержит исправление безопасности, пользователям ARM рекомендуется использовать бета-версии 1.14, в которых скоро появится исправление для ARM.

Стабилизация языка

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

Возможности Cargo

Более детальный список изменений доступен по ссылке: https://github.com/rust-lang/rust/blob/stable/RELEASES.md#version-1130-2016-1...

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

★★★★★

Проверено: maxcom ()
Последнее исправление: cetjs2 (всего исправлений: 5)

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

C точностью до наоборот: вариант с итераторами будет быстрее.

А это уже личная проблема компилятора Rust и его оптимизаций, вероятно для итераторов там играет size_hint, а для вектора руками ты reserve не дернул. Я у себя сравнил итераторы на Rust и простой цикл на С++, C++ без reserve на около 5% быстрее, а с reserve - в полтора раза.

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

1. Надеюсь вы C++ прогу собирали с помощью clang, иначе тест ни о чём.

2. Какой бенчмарк-фреймворк вы использовали? Или просто запустили прогу пару раз?

3. Сколько инструкций в каждом из вариантов?

Ну а так да, в «бенчмарках» «быстрее».

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

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

Ну вот прям как Modula-2. По словам тех, кто использует ее для написания надежного софта, просто замечательный язык. Только вот мало их.

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

документации

Вот с чем-чем, а с докой в rust всё прекрасно, ибо она встроена в язык. 99% кода покрыто пусть и примитивной, но документацией. В любой момент можно зайти на docs.rs и почитать доку к любому пакету из cargo.

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

Приведите пример:

vec.iter().filter(|n| n > 0).map(|n| n * 2).collect()

copy_range<vector<int>>(vec | filtered([](auto n) { return n > 0; }) | transformed([](auto n) { return n * 2; }));
DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

Там такой же GC, как в C++ на основе shared_ptr/weak_ptr.

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от quantum-troll

И рекурсия — добро, правда только в тех языках, которые всегда готовы её оптимизировать.

А у тебя что, есть примеры, где проще сделать хвостовую рекурсию, чем цикл или какой-нибудь iterate?

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

Фанаты Oberon-а лет 15 назад говорили приблизительно то же самое.

Ну и очевидно, что в слово «документация», мы с вами вкладываем разный смысл.

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

Что, ты тоже возлюбил цепочки с многострочными лямбдами?

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

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

Гвидона на тебя нет, еретик :D

А что, из питона итераторы уже выпилили?

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

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

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

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

я вижу поточную обработку данных. Я возлюбил ее с первого знакомства с шеллом.

Да-да, шелл, перл, борщ... ах, какой аромат!

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

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

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

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

Число - это моя опечатка, должно было быть три типа. (:

и так, разницы никакой:

Разница как раз есть. Я же не зря об «единообразии» заговорил: удобно когда объявляется и матчится одинаково.

И да, let (a, b, c) определяет три константы. Точно так же, как и при использовании внутри match.

let [a, b, c] = tuple<int32, int32, int32>(10,20,30);

А если мы тут тип захотим указать? В расте, опять же, оно будет выглядеть одинаково:

let (a, b, c): (i32, i32, i32) = (10, 20, 30);

Спорить бесполезно, от этих споров в Rust-е ничего не изменится.

Пусть не изменится, но я пользу от (некоторых) споров вижу. Более того, раст не кажется идеальным в 100% случаев, бывает и так, что в других языках, причём не обязательно новых, может быть сделано удачнее - почему бы и не посмотреть на чужой опыт.

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

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

У меня несколько другое впечатление. Тот же C++ большой и древний монстр, который бы хотелось заменить на что-то более современное и вменяемое. Но пока за C++ играет как наличие огромного легаси (и, значит, реальных денег), так и темпы развития самого C++ за последние 6-7 лет (особенно если сравнивать с тем, что было в начале 2000-х).

Тем не менее, современный нативный язык без GC, с большими гарантиями безопасности, без наследия C, с быстрой компиляцией, с системой модулей, с аналогом Cargo (RubyGems, Maven, ...), с элементами ФП (в первую очередь АлгТД+паттерн-матчинг, возможность объявлять «чистые» функции) мне бы лично было бы приятно увидеть и пробовать в деле.

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

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

Число - это моя опечатка, должно было быть три типа. (:

О том и речь. Многословность имеет свои преимущества.

let (a, b, c): (i32, i32, i32) = (10, 20, 30);

Ну и что тут хорошего: во всех трех случаях круглые скобки определяют разное.

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

Ну и что тут хорошего: во всех трех случаях круглые скобки определяют разное.

Разные варианты одного и того же: кортеж как образец, кортеж как тип и кортеж как значение.

quantum-troll ★★★★★
()
Ответ на: комментарий от eao197

Ну и для повторения: у C++ далеко не идеальный синтаксис.

Да.

Но синтаксис Rust-а выглядит даже хуже.

Нет.

Хотя все споры о синтаксисе — это на 99% вкусовщина.

Да. (Хотя о проценте я, опять же, поспорил бы.)

Таки и нужно было пенять.

Ну и как бы ты это пофиксил? Опять же, в он в D «решили вопрос» и используют для шаблонов всё те же круглые скобки, правда с «костылём» в виде восклицательного знака.

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

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

С выразительностью похуже, это да. Хотя если collect тип, так или иначе, указать, то станет более сравнимо. С лямбдами правда всё плохо, но тут какой-нибудь свифт у раста выиграет.

std забыли.

Не забыл, там юзинг. (:

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

О том и речь. Многословность имеет свои преимущества.

Может и имеет, но уж точно не в этом случае. Если ты в tuple вместо uint8_t напишешь uit8_t, то тебе компилятор (обоих языков, что характерно) честно скажет «ожидался тип, а вместо него фигня какая-то».

Ну и что тут хорошего: во всех трех случаях круглые скобки определяют разное.

Сущность одинаковая. Действия разные, да. Ну и неужели, вот такое вот лучше?..

let [a, b, c]: tuple<int32, int32, int32> = tuple<int32, int32, int32>(10,20,30);

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

А когда видишь f (a, b, c), то это почти как все перечисленное, но это уже вызов функции.

А когда видишь F(a, b, c), то это уже создание именованного кортежа. Впрочем, я всё это чётко различаю и читать код мне это совсем не мешает.

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

Ну и неужели, вот такое вот лучше?..

let [a, b, c]: tuple<int32, int32, int32> = tuple<int32, int32, int32>(10,20,30);

До абсурда можно довести все, что угодно. Конструировать экземпляр кортежа вполне можно и через (). Т.е. запись (10,20,30) для создания экземпляра не лучше и не хуже, чем запись {10,20,30} или, допустим, <10,20,30>.

Речь шла про то, что определять тип кортежа через () для меня выглядит как-то странно и непоследовательно. Так что если делать полную запись, то как-то так:

let [a, b, c]: tuple<int32, int32, int32> = (10,20,30)

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

Вообще-то, когда в сложном выражении намешаны C-style-casts, то это изрядно затрудняет чтение. Например, когда видишь что-то в духе:

int zz[] = { 0, ((void)f(std::forward<Ts>(ts)), 0)... };
то далеко не сразу понимаешь где какие скобочки что означают.

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

С лямбдами правда всё плохо, но тут какой-нибудь свифт у раста выиграет.

Разве? По-моему в свифт еще больше писанины: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Sw...

Не забыл, там юзинг

Уж лучше std

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

Речь шла про то, что определять тип кортежа через () для меня выглядит как-то странно и непоследовательно.

Если тип ссылок выглядет как &T, а тип массивов — как [T], то что такого в том, что тип кортежей пишется через ()? Это вполне естественно.
Другое дело, что значение (10,20,30) может выглядеть, как что-то другое. Но на практике, проблем с этим нет.

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

Если тип ссылок выглядет как &T, а тип массивов — как [T], то что такого в том, что тип кортежей пишется через ()? Это вполне естественно.

&T, [T], T<K,V>, (K,V)... Естественно, говорите. Ну пусть так.

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

v.sorted {$0 < $1}

Этих тоже шеллоперл покусал.

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

Речь шла про то, что определять тип кортежа через () для меня выглядит как-то странно и непоследовательно. Так что если делать полную запись, то как-то так:

let [a, b, c]: tuple<int32, int32, int32> = (10,20,30)

Как по мне, то в расте как раз получается последовательнее. Видимо, опять вкусовщина.

Но разве после этого не будет претензии в духе «почему квадратные скобки означают и массив и „structural binding“»?

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

b.cmp(a) != <

Без разницы, тут показана сортировка, а не сравнение, все-равно Rust многословней.

индексы - это совсем хардкор

Для однострочных лямбд самое оно, для многострочных имена легко добавляются.

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

Да там не принципиально, квадратные ли скобки или круглые. Можно вообще без скобок. Претензия была к тому, что тип тупла в круглых скобках не выделяется, в отличии от tuple<types...>. Ну и HashMap<K,V> почему-то является чем-то другим, нежели (K,V).

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

По-моему в свифт еще больше писанины:

filter(|n| n > 0)
filter{$0 > 0}

В языке много всякого разного сахара для частных случаев.

Уж лучше std

И почему же?

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

Если бы еще более вменяемых растоманов сюда завезли, было бы вообще хорошо.

Разве бывают вменяемые сектанты?

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

А вот в Go всё совсем печально:

slice.Sort(planets[:], func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

Возможно есть более каноничный вариант.

Полноценная сортировка вообще эпично сделана: https://gobyexample.com/sorting-by-functions. Очень странный язык...

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

Ну и HashMap<K,V> почему-то является чем-то другим, нежели (K,V).

А почему они быть одинаковыми? В первом случае, ассоциативный контейнер, во втором - кортеж. Какая между ними связь кроме того, что значения в контейнере могут быть представлены парами?

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

В частном случае - да.

Да (практически?) в любом. Обратные примеры будут? В свифте «не стесняются» вводить сахар, да и вообще удобство у них в большем приоритете, чем «зерокост».

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

Посмотрите примеры по ссылке.

Более «правильным» вариантом будет:

v.sorted(by: { a, b in a < b } )

Хотя

v.sorted(by: >)
конечно не переплюнуть.

Я к свифту никаких претензий не имею. Но без поддержки винды он мне не нужен. Да и поддежка линя тоже странная. Вон раст уже давно во всех популярных дистрах, а swift под гентой я завести так и не смог.

PS: дока у них криво оформлена. Код полупрозрачный какой-то в тексте.

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

Посмотрите примеры по ссылке.

Дык, там приведены полные формы записи. Смысл на них равняться, если есть сокращённые?

Я к свифту никаких претензий не имею

Ну а мне он наоборот не интересен, но это не значит, что код на нём не может быть короче.

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

А я что предлагаю? Первый пришедший в голову вариант:

impl T for S use S.a {
    fn qux(&self) { /* не делегируем, новая реализация */ }
}

Чем это менее явно, чем то, что необходимо писать сейчас?

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

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

Дык, процедурные макросы тоже работают на этапе компиляции - они тоже позволят сделать «не лучше чем в Qt». Или я чего-то не так понял?

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

Ну и как по мне, проблема ещё в том, что сигналы-слоты из-за системы владения так просто не изобразишь

ИМХО, не так уж и сложно. Понятно, что нужны raw-указатели, хранилище raw-указателей на связанные объекты и чистка ссылок на себя в drop'е. Ну и move-семантика мешает, raw-указатели сломает, но это реализуемо через box на приватную структуру, хранящую пользовательские данные внутри. Должно быть что-то типа: https://is.gd/ruCwie

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