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)

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

Спасибо. Не понятно, правда, зачем параметр iter объявлен как мутабельный. Но иллюстрация моих слов хорошая.

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

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

Вы тоже хотите поговорить про бесконечно малые величины? Фраза «чуть меньше, чем полностью» означает, что синтаксис двух языков практически полностью совпадают. Насколько, что программа на одном языке является программой и на другом. Что, очевидно, не так. И пример был выше показан. Уже и не один.

То есть всякие :: . -> & * | >> вам прекрасно понятны, а как только появляется ' - язык сразу превращается в perl?

Внезапно: всякие :: . -> & * | >> => есть и в Rust-е. Только там они еще приправлены магическими сочетаниями &'х, mut &'y. При том, что я не большой фанат синтаксиса C++, но разработчики Rust-а умудрились таки пробить дно.

Ну и да, сравнение с perl-ом делал не я.

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

зачем параметр iter объявлен как мутабельный.

Чтобы не объявлять изменяемую локальную переменную.

Но иллюстрация моих слов хорошая.

Этих?

в валидный Rust-код, то Rust-у было бы гораздо проще занять свое место под Солнцем

Стоит заметить, что в Rust версии паника при значении большем чем в коллекции, а в C++ выход за границы.

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

Ты tcl видел?

Эта хитрая игра с выполнением генеренного синтаксиса и экранирования. Ух....

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

Этих?

Этих.

Стоит заметить, что в Rust версии паника при значении большем чем в коллекции, а в C++ выход за границы.

C++ный алгоритм не выходит за границы [first, last). Ну и да, получить панику в случае, если с first/last что-то не так, без переписывания кода на новом языке и является целью.

Другое дело, что эта цель кроме меня никому не интересна.

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

а как только появляется ' - язык сразу превращается в perl?

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

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

&'х, mut &'y. При том, что я не большой фанат синтаксиса C++, но разработчики Rust-а умудрились таки пробить дно.

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

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

И я могу ошибаться, но вполне вероятно, что ' был выбран из-за того, что в первых версиях всякие @, ~ уже были заняты, и выбирать было особо не из чего.

Да, ты ошибаешься.

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

Как вы хитро излагаете. Значит если язык многословен - он хороший, только потому, что вам так нравится.

Но если вы считаете что это вкусощина, тогда это 4.2 и обсуждать тут вообще нечего.

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

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

Я считаю, что я — Бог.

Докажите обратное.

Никаких доводов против я не услышал.

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

Как вы хитро излагаете.

Что уж тут хитрого. Вы сказали «синтаксис раста совпадает с С++ чуть менее чем полностью». Вам показали, что это не так. Неоднократно. Но вы никак не можете признать, что сказали херню.

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

Мне нравится, когда важные вещи отмечаются настолько явно, что их сложно пропустить даже при беглом взгляде. В записи &'a Vec<u32> легко не обратить внимание на апостроф. Да и для того, чтобы расшифорвать u32 как unsigned int 32-bit нужно так же иметь некоторую сноровку. Поэтому я бы предпочел иметь другую запись. Вроде borrow<a> Vec<uint32> или Vec<uint32> in a.

Но если вы считаете что это вкусощина, тогда это 4.2 и обсуждать тут вообще нечего.

Обсуждать здесь можно важный фактор: насколько вид Rust-ового кода стимулирует желание обратить на Rust внимание. Вкусовщина здесь играет не последнюю роль.

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

Вам показали, что это не так. Неоднократно.

И с каких пор нужно прислушиваться к мнению хейтеров?

Мне нравится

Я и не указываю вам, что вам должно нравится. В отличии от...

насколько вид Rust-ового кода стимулирует желание обратить на Rust внимание

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

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

И с каких пор нужно прислушиваться к мнению хейтеров?

Т.е. если я скажу, что 2+2=4, то вы к этому не прислушаетесь, потому, что это сказал хейтер?

В отличии от...

В отличии от чего? Цитату может показать, где и кому я что-то указывал?

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

Откуда взялось «только синтаксис»? Интерес к языку — это многофакторная штука, синтаксис здесь далеко не на последнем месте, особенно при первом знакомстве.

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

Вам показали, что это не так. Неоднократно.

И с каких пор нужно прислушиваться к мнению хейтеров?

Д'Артаньян в белом в треде! Текаем посоны!

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

насколько вид Rust-ового кода стимулирует желание обратить на Rust внимание. Вкусовщина здесь играет не последнюю роль

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

И это еще при том что OCaml считаю очень клевым по читаемости.

anonymous
()

А подскажите, есть ли для Rust какие-нибудь системы логического вывода?

Есть в задумке проект с упором на скорость, компактность и корректность. Решать предполагалось на C + Frama-C. Rust выглядит хорошим вариантом, но сам по себе он предлагает только физическую корректность, а есть ли чем проверить логическую?

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

Современные кресты уже читаются лучше

Херасе. Вот это читается лучше?

auto doEmplaceAssign(long, T& t, Us&&... us)
    -> decltype(void(T((Us &&)us...))) {
  t.~T();
  ::new ((void*)std::addressof(t)) T((Us &&)us...);
}

а безопасности предлагают немногим меньше

А это просто вранье.

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

Вот это читается лучше?

Это, походу, не такие уж и современные плюсы. В современных было бы что-то вроде:

auto doEmplaceAssign(long, T& t, Us&&... us) {
  t.~T();
  ::new (std::addressof(t)) T(std::forward<Us>(us)...);
}

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

безопасности предлагают немногим меньше

UB при обращение к объекту после move уже «исправили»?

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

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

А это просто вранье

Только не надо опять про use-after-move и т.п.

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

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

Синтаксически будет примерно одинаково понятно. Не Python, конечно, но ничего ужасного.

Только не надо опять про use-after-move и т.п.

Почему не надо? А про лайфтаймы тоже не надо?

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

Нет, всего лишь С++14. Более того, не очень понятно, зачем там изначально был decltype. И зачем нужно использовать auto для вывода типа результата. И зачем там std::addressof. Вроде как даже в рамках C++11 это переписывается следующим образом:

void doEmplaceAssign(long, T& t, Us&&... us) {
  t.~T();
  ::new (&t) T(std::forward<Us>(us)...);
}
С++ далеко не самый удачный язык с точки зрения удобства чтения программ, но здесь то какие претензии?

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

Так может поговорим про UB в коде вида: shit getShit() { return shit(); } auto shit = takeShit(getShit()); shit.useOurShit(); Которые работают/падают от фазы луны и попадаются чаще, чем проект на плюсах запускается с первого билда? Этой фигне лет какплюсам — очень показательно.

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

Тебе не нравятся иероглифы — но при этом в плюсах есть всё то же самое кроме апострофа. Чтение того же шаблонного кода наплюсах — это вообще космос, несравнимый даже с перлом.

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

но разработчики Rust-а умудрились таки пробить дно.

Хоть кто-то имеет смелость это написать. А то тылганер всех уже запугал бешеным плюсометом.

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

shit getShit() { return shit(); } auto shit = takeShit(getShit()); shit.useOurShit();

Без кода takeShit() не понятно, что хотел сказать автор.

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

Чтение того же шаблонного кода наплюсах — это вообще космос, несравнимый даже с перлом.

Ты ржавые макросы то видел? :D

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

Херасе. Вот это читается лучше?

Us&&... - в Rust такого нет, так что да, это лучше чем аналогичный макрос;

-> decltype(void(T((Us &&)us...))) - SFINAE, аналогично, в Rust нет ни перегрузки функций, ни аналога;

::new () - placement new, в Rust таки уже есть аналог в experimental, особой разницы в читабельности нет;

(void*) - нахождение начального адреса объекта, т.к. может быть использовано наследование, которого в Rust так же нет;

std::addressof(t) - защита от перегрузки оператора &, который в Rust нельзя перегрузить;

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

А это просто вранье.

Это да.

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

Это, походу, не такие уж и современные плюсы. В современных было бы что-то вроде:

decltype тут для SFINAE, функция возвращает void. А так это вполне себе современный C++11 код.

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

защита от перегрузки оператора &, который в Rust нельзя перегрузить;

Оба можно перегрузить: https://doc.rust-lang.org/std/ops/trait.BitAnd.html + https://doc.rust-lang.org/book/deref-coercions.html

нет ни перегрузки функций, ни аналога
ни аналога

Есть через trait From.

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

Касты не проверяются ни во время компиляции ни в рантайме, а значит совершенно небезопасны.

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

https://doc.rust-lang.org/std/ops/trait.BitAnd.html

Хорошая шутка.

Есть через trait From.

Тоже ничего.

Касты не проверяются ни во время компиляции ни в рантайме, а значит совершенно небезопасны.

Отлично проверяются, если использовать сиплюсные касты, а не сишные.

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

Отлично проверяются, если использовать сиплюсные касты, а не сишные.

struct S1 {
    int x = 1;
};

struct S2 : S1 {
    int y = 2;
};

struct S3 {
    int z = 3;
};

S2 *s2 = new S2;
void *s3 = static_cast<void*>(s2);
S3 *s1 = static_cast<S3*>(s3);
std::cout << s1->z << std::endl; // 1
RazrFalcon ★★★★★
()
Ответ на: комментарий от eao197

Да не особо важно что там, ошибка в любом случае рано или поздно вылезет. Вот допустим:

shit& takeShit(shit& myShit) {
auto clientsNeed = Shit::SOME_SHIT_TYPE;
return myShit.findSomePeace(clientsNeed);
}

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

Ну так их и не корректно сравнивать. Хотя несмотря на страшные символы их семантика по крайнней мере чётко ограничена.

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

Если через void* кастить, то да, но это уже специально надо стараться прищемить себе тестикулы. А кастинг между связанными типами проверяется, в том числе в рантайме через dynamic_cast.

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

Потому что не сталкивались — в зависимости от оптимизаций компилятора и стадии луны shit не доживает до useOurShit()

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

Кстати да, по единственной реализации doEmplaceAssign не догадался для чего decltype. Если посмотреть на весь код, то становится понятно что к чему и почему:

template <class T, class U>
auto doEmplaceAssign(int, T& t, U&& u) -> decltype(void(t = (U &&)u)) {
  t = (U &&)u;
}

template <class T, class U>
auto doEmplaceAssign(long, T& t, U&& u) -> decltype(void(T((U &&)u))) {
  t.~T();
  ::new ((void*)std::addressof(t)) T((U &&)u);
}

template <class T, class... Us>
auto doEmplaceAssign(int, T& t, Us&&... us) -> decltype(void(t = T((Us &&)us...))) {
  t = T((Us &&)us...);
}

template <class T, class... Us>
auto doEmplaceAssign(long, T& t, Us&&... us) -> decltype(void(T((Us &&)us...))) {
  t.~T();
  ::new ((void*)std::addressof(t)) T((Us &&)us...);
}
Я бы, правда, вместо (Us &&) предпочел std::forward<Us>(us), а то в скобочках начинаешь путаться. Ну и приведение к void* через сишный каст... clang бы на высоких уровнях предупреждений на это бы ругался.

Кстати, по поводу (void*)std::addressof(t) — это что, реально как-то работает в случае наследования?

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

Могли бы вы привести нормальный пример? А то ведь если есть

shit getShit() { return shit(); }
То нельзя написать вот так с вашим takeShit():
takeShit(getShit())
https://godbolt.org/g/CKXzP4

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

А вы плюсовые видели? Ах да - их нет.

В Rust макросы тоже крайне примитивные. Финтов ушами как в Nim, D или лиспах на них не сделать. А еще их зачастую приходится использовать там, где в плюсах хватает средств самого языка. А это большой минус.

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

А это большой минус.

Код, генерируемый шаблонами и макросами так сильно отличается друг от друга?

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

А никто и не говорит, что они совершенны. Мы тут C++ и Rust сравниваем.

На тогда в С++ макросов почти нет, а в Rust они немного есть. Без проблем.

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

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

Почему не надо?

Потому что непонятно, кто в здравом уме напишет move и продолжит пользоваться объектом.

А про лайфтаймы тоже не надо?

Надо, но что-то движется. Пока есть unique_ptr/Owner, анализаторы подтягиваются. В большинстве случаев хватает.

anonymous
()

Я смотрю, у вас тут очень теплая обстановка.

На какой год запланировано окончательное решение сифтардского вопроса?

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

кто в здравом уме напишет move и продолжит пользоваться объектом.

Человек?

unique_ptr
лайфтаймы

Мы точно про одно и тоже говорим?

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