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)

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

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

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

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

Последние два, насколько я знаю, используют полуавтоматический GC. То есть нельзя чётко указать, что это выделяй на стеке, а это в куче. Ну и существуют они только в экосистеме apple.

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

в продакшыне тебя будут бить, возможно ногами

Не будут. Даже в джаву лямбды завезли и активно используют.
XXI век, обычный код больше не состоит из переливания пустого в порожнее в стиле for while i++.

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

Чтобы добавить пару строчек в этот код нужно... просто добавить их. А теперь займемся рефакторингом (или еще хуже отладкой) однострочника...

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

Не будут.

Я гарантирую это. А лямбды завезли не для того, чтобы играть ими в перл-гольф.

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

Какой перл-гольф? Тот кусочек кода прямой, как железная дорога.

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

Ок. Забудем про термин синтаксис. Объясняю на пальцах: когда я начал учить Rust, я просто открыл доку и начал смотреть примеры кода. Не читать описания и прочее, чисто примеры. Весь базовый уровень, ака структуры, функции, условия и циклы были понятны просто по примерам кода. Мне не нужно было читать описания, не нужно было их запускать. Да, порядок кое-где другой; да, тут let вместо auto. Но общая картина не меняется. Садишься и пишешь.

Теперь берём Haskell. Когда я вижу пример кода на Haskell, я совершенно не понимаю что в нём происходит. Для меня это просто набор символов. Упомянутый ocaml получше, но я всё равно не понимаю как он устроен и предугадать вывод программы почти нереально.

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

чтобы и там, и там идиоматический код применялся

Ваш вариант не имеет никакого отношения к идиоматичности. Всё отличия задаются посредством Coding Style. Выбор printf или cout также зависят от проекта.

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

Лучше расскажите, зачем каждый раз писать шаблонный for/while?

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

И почему ты считаешь это хуже, чем:

Как минимум, оно будет медленнее за счет большего числа итераторов, которые будут дергать друг-друга.

// Другой анонимус, которого устраивает вариант на Rust

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

Не будут, так как компилятор оптимизирует всё в цикл.

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

Надеюсь теперь понятно, что я подразумевал под словом «синтаксис»?

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

Ваш вариант не имеет никакого отношения к идиоматичности.

Отказ от использования printf-семейства в C++ коде идеоматичен уже лет 25. Но для вас стандатная библиотека C++ говно, поэтому вам привычнее пользоваться в C++ устаревшим С-шным говном. Из-за чего и возникает непреодолимая потребность сбежать из C++ в Rust.

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

Последние два, насколько я знаю, используют полуавтоматический GC.

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

Ну и существуют они только в экосистеме apple.

По сравнению с тем, насколько они востребованы, Rust-а просто не существует.

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

Чтобы добавить пару строчек в этот код нужно... просто добавить их.

Что мешает их добавить в тот код?

А теперь займемся рефакторингом

Нечего там рефакторить, всё предельно чисто и ясно.

или еще хуже отладкой

А что, пользоваться головой и отладочным выводом ты и твои коллеги не умеют?

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

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

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

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

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

Как минимум, оно будет медленнее за счет большего числа итераторов, которые будут дергать друг-друга.

Обычно там всё инлайнится.

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

Что мешает

Что мешает переписать однострочник в многострочник вместо того, чтобы сразу писать нормально? Да ничего. Наши руки не для скуки!

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

Прочитайте всю статью по ссылке.

Читал. И? Предположим, я хочу при разыменовании что-то делать, пусть будет просто выводить строку «deref!». Какой код надо дописать, чтобы это работало?

struct T;

fn main() {
    let t = T;
    &t;
}
Если нельзя, то это нефига не перегрузка &.

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

Вы извините, но более лютое говно, чем плюсовое io ещё поискать надо. *printf используют именно из-за его наглядности и краткости. А то что это не вписывается в идею стримов - ну и хер с ней, с этой идеей.

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

А теперь займемся рефакторингом

Если это понадобится - в чем проблема отрефакторить одну строчку?

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

Добавил:

vec.into_iter()
    .filter(|&n| n > 0).map(|n| n * 2)
    .enumerate().map(|(i, v)| {
        if i % 3 == 0 { v^2 }
        else { v^3 }
    })
    .collect();

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

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

Что мешает переписать однострочник в многострочник вместо того, чтобы сразу писать нормально?

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

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

Да, синтаксис раста шумный, да, каждый символ имеет значение, но всё можно прочесть и понять с одного взгляда

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

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

А то что это не вписывается в идею стримов - ну и хер с ней, с этой идеей.

fmtlib. Все вписалось куда нужно, и как нужно. А если мозгов нет, да еще и руки из жопы, то да, тут уже ах и iostreams говно.

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

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

Ну-ка добавь мне отладочную печать в однострочник, умняшка. Ой, опять все переписывать.

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

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

Мне это не очевидно. С туплами, по моему, получилось как раз очень здорово. Особенно, если вспомнить про паттерн матчинг:

let t: (i32, i32, 32) = (10, 20, 30);
let (a, b, c) = t;
let t: tuple<int32, int32, int32> = tuple<int32, int32, int32>(10, 20, 30);
let ??? = t;
Да, типы намерено указаны: для иллюстрации однообразия и для второго варианта - многословности.

Про лайфтаймы тоже можно поспорить.

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

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

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

Добавил

Нормально, чо. Методом полного переписывания получили вырвиглазный песец. Вместо того, чтобы просто добавить пару строк. Наши руки не длля скуки! (2)

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

Годный, очень годный перл! Продолжайте в том же духе.

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

Ну-ка добавь мне отладочную печать в однострочник, умняшка.

vec.iter().filter(|&&n| n > 0).inspect(|n| println!("{}", n)).map(|&n| n * 2).collect()

Ой, опять все переписывать.

Столько переписал, аж запыхался.

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

Еще раз: diff покажи. Ты развернул однострочник в уродскую многострочную цепочку с лямбдами, необходимость которых здесь сомнительна. И так будет с каждым однострочником в реальном коде. Следующая итерация рефакторинга - разбить цепочку, избавиться от лямбд.

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

Ну и лично для меня есть некая логическая непоследовательность в том, что HashMap от K и V записывается как HashMap<K,V>, а тупл из K и V, как (K,V), вместо Tuple<K,V>.

Но ведь тебя не смущает, что в плюсах есть [int] и array<int, 10>. Хотя претензию более-менее понимаю: меня в своё время напрягало, что «ассоциативные массивы» в D имеют свой отдельный синтаксис. Но кортежами в плюсах пользоваться не особо удобно, так что такую вещь можно и «встроить» в язык.

А ещё в расте нет вариадиков (для дженериков). Может дело в этом.

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

Можно конечно сразу писать по человечески, но хипстерам это скучно.

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

let t: (i32, i32, 32) = (10, 20, 30);

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

С вашими вопросиками непонятно, в чем проблема. Можно и так:

let (a,b,c) = tuple<int32, int32, int32>(10,20,30);
и так, разницы никакой:
let [a, b, c] = tuple<int32, int32, int32>(10,20,30);
Последний вариант по мотивам structural binding из C++17.

Ну и кстати, вот здесь:

let (a, b, c) = ...
определяется что? Один кортеж? Или три независимые константы?

Про лайфтаймы тоже можно поспорить.

Спорить бесполезно, от этих споров в Rust-е ничего не изменится. Скорее это попытка объяснить растоманам (особенно с узким взглядом на мир), почему Rust не заставляет людей приходить в восторг и писать кипятком от радости.

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

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

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

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

Еще раз: diff покажи.

У вас гитчанка.

И так будет с каждым однострочником в реальном коде.

Ну и хорошо. Нажать Enter пару раз труда не составляет.

Следующая итерация рефакторинга - разбить цепочку, избавиться от лямбд.

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

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

vec.iter().filter(|&&n| n > 0).inspect(|n| println!(«{}», n)).map(|&n| n * 2).collect()

Ага, специальный костыль для отладки цепочек. Хипстота просто гениальна :D

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

Или три независимые константы?

Они самые. Рассматривай это как «присвоить a, b и c такие значения, чтобы кортеж (a, b, c) был равен ...».

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

Только в плюсах мы всё пишем в руками, а в том же Swift всё автоматом.

Речь о том, что GC там не отличается от аналогичного GC в C++. Поэтому Swift топчет ту же поляну, что и C++, и Rust, и Ada, и Modula-2. А не ту, где оказались D, Go, Eiffel и прочие нативные языки с полноценной сборкой мусора.

Да, cout более идиоматичный с точки зрения std.

Он не только с точки зрения std идеоматичен. Посмотрите на свой код:

let i = 0;
if i == 0 {
    println!("{}", i);
}
Поменяйте начальное значение для i на вещественное и у вас больше ничего не изменится:
let i = 0.01;
if i == 0 {
    println!("{}", i);
}
Или даже на строковую константу поменяйте. И опять у вас ничего не изменится кроме инициализации i.

В C++, если вы используете printf, вы бодренько побежите по граблям и опять начнете вопить про небезопасность и отсутствие контроля. В случае с cout-ом же вы получаете нормальную типобезопасность.

Тем не менее, есть полно проектов, которые его не используют.

Дык, некоторые в C++ и множественное наследование не используют, и шаблоны, и исключения. А довольствуются лишь наследием C и малой толикой возможностей C++. Даже к ссылкам относятся с подозрением, мол только указатели, только это православно. И исключительно по религиозным соображениям.

Даже Qt использует свой аналог.

Вы слишком долго жили в мире Qt.

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

Ага, специальный костыль для отладки цепочек.

Интересно, что ты скажешь о gdb. Специальный, сцуко, костыль для отладки программ.

Хипстота просто гениальна :D

Let the butthurt flow through you.

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

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

Таки и нужно было пенять. И потребовалось ждать C++11, дабы не приходилось пробелы между закрывающими скобками шаблонов расставлять.

Ну и для повторения: у C++ далеко не идеальный синтаксис. Но синтаксис Rust-а выглядит даже хуже. Хотя все споры о синтаксисе — это на 99% вкусовщина.

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

Скорее это попытка объяснить растоманам (особенно с узким взглядом на мир), почему Rust не заставляет людей приходить в восторг и писать кипятком от радости.

Скорее, это попытка объяснить, почему Rust не заставляет тебя приходить в восторг и писать кипятком от радости. Даже дети знают, что всем не угодишь.

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