LINUX.ORG.RU

Rust 1.31.0 (2018)

 ,


5

10

Команда Rust объявила о выходе новой стабильной версии Rust 1.31.0, который ознаменует собой также выход новой редакции «Rust 2018». Rust — это язык программирования, который позволяет каждому создавать надежное и эффективное программное обеспечение.

Если у вас установлена предыдущая версия Rust, обновиться до Rust 1.31.0 проще всего следующим образом:

rustup update stable

Если у вас ещё не установлен Rust, то это можно сделать, загрузив с сайта утилиту rustup.

Что нового в Rust 1.31.0

Rust 2018

Данный релиз ознаменует собой выпуск редакции Rust 2018. Впервые Rust 2018 был упомянут в марте, затем в июле: прочтите их, чтобы понимать для чего нужен Rust 2018. Также, есть статья на сайте Mozilla Hacks.

Вкратце, Rust 2018 это возможность представить всю работу за последние три года в виде цельного пакета. Кроме возможностей языка, сюда входят:

  • Инструментарий (поддержка IDE, rustfmt, Clippy)
  • Документация
  • Работа различных рабочих групп
  • Новый веб-сайт

Для обозначения редакций Rust был представлен ключ edition в Cargo.toml:

[package]
name = "foo"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"

[dependencies]

Значение 2018 означает, что используется редакция Rust 2018; отсутствие ключа или значение 2015 означает использование редакции Rust 2015.

Важно отметить, что каждый пакет может быть в редакциях 2015 или 2018, и они без проблем могут работать вместе. Проект под редакцией 2018 может использовать зависимости 2015, а проект 2015 использовать зависимости 2018. Это гарантирует целостность экосистемы, сохраняя совместимость существующего кода. Кроме того, существует возможность автоматической миграции кода с редакции Rust 2015 на Rust 2018 при помощи cargo fix.

Non-lexical lifetimes (NLL; Нелексические времена жизни)

В 2018 появились нелексические времена жизни, что на простом языке означает, что проверщик заимствований (borrow checker) стал умнее и теперь не отклоняет правильный код. Например:

fn main() {
    let mut x = 5;

    let y = &x;

    let z = &mut x;
}

В старых версиях этот код выдаст ошибку компиляции:

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:18
  |
4 |     let y = &x;
  |              - immutable borrow occurs here
5 |     let z = &mut x;
  |                  ^ mutable borrow occurs here
6 | }
  | - immutable borrow ends here

из-за того, что время жизни действует внутри лексического блока; то есть, заимствование будет удерживаться за y, пока y не покинет пределы main, несмотря на то, что y больше не используется. Сегодня этот код скомпилируется правильно.

Другой пример:

fn main() {
    let mut x = 5;
    let y = &x;
    let z = &mut x;
    
    println!("y: {}", y);
}

Старый Rust выдаст следующую ошибку:

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:18
  |
4 |     let y = &x;
  |              - immutable borrow occurs here
5 |     let z = &mut x;
  |                  ^ mutable borrow occurs here
...
8 | }
  | - immutable borrow ends here

В Rust 2018 вывод ошибки стал лучше:

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:13
  |
4 |     let y = &x;
  |             -- immutable borrow occurs here
5 |     let z = &mut x;
  |             ^^^^^^ mutable borrow occurs here
6 |     
7 |     println!("y: {}", y);
  |                       - borrow later used here

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

Пока эти возможности доступны в Rust 2018, но в будущем планируется портировать их на Rust 2015.

Изменения в системе модулей

Редакция Rust 2018 привносит некоторые изменения в работу с путями, что в конечном итоге привело к упрощению системы модулей.

Вкратце:

  • extern crate больше не требуется практически во всех случаях.
  • Макросы теперь можно импортировать при помощи use вместо атрибута #[macro_use].
  • Абсолютные пути начинаются с названия пакета, где ключевое слово crate ссылается на текущий пакет.
  • foo.rs и поддиректория foo/ могут сосуществовать вместе; mod.rs больше не нужен при размещении подмодулей в поддиректории.

Полную информацию можно прочесть в руководстве.

Упрощенные правила синтаксиса времени жизни

В обеих редакциях представлены новые правила синтаксиса времени жизни для блоков impl и определениях функций. Следующий код:

impl<'a> Reader for BufReader<'a> {
    // methods go here
}
теперь может быть написан таким образом:
impl Reader for BufReader<'_> {
    // methods go here
}

'_ подсказывает, что BufReader берёт параметр, но больше нет необходимости именовать его.

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

// Rust 2015
struct Ref<'a, T: 'a> {
    field: &'a T
}

// Rust 2018
struct Ref<'a, T> {
    field: &'a T
}
: 'a добавляется автоматически. При желании, можно продолжать использовать явное определение.

const fn

Существует несколько способов определения функций в Rust: регулярная функция с fn, небезопасная функция с unsafe fn, внешняя функция с extern fn. В этом релизе появился ещё один способ: const fn, который выглядит следующим образом:

const fn foo(x: i32) -> i32 {
    x + 1
}
Функции const fn могут вызываться как регулярные функции, но вычисляются во время компиляции, а не во время выполнения. Для стабильной работы, они должны иметь детерминированный результат и в настоящее время ограничены следующим минимальным набором операций:

  • Арифметические операторы и операторы сравнения с целыми числами
  • Все логические операторы, кроме && и ||
  • Построение массивов, структур, перечислений и кортежей
  • Вызов других функций const fn
  • Задание индекса массивам и срезам
  • Доступ к полям структур и кортежей
  • Чтение из констант
  • & и * на ссылках
  • Приведение типов, за исключением необработанных указателей на целые числа

В будущем данный набор будет расширяться, подробную информацию можно посмотреть здесь.

Новые инструменты

Наряду с Cargo, Rustdoc, и Rustup, которые являются ключевыми инструментами с версии 1.0, редакция 2018 представляет новое поколение инструментов: Clippy, Rustfmt, и поддержку IDE.

Clippy является статическим анализатором кода в Rust, достиг версии 1.0 и теперь доступен в стабильной версии Rust. Установку можно произвести следующим образом: rustup component add clippy, запуск: cargo clippy.

Rustfmt является инструментом для автоматического форматирования кода Rust в соответствии с официальной стилистикой Rust. В этом релизе он достиг версии 1.0 и, начиная с этой версии, гарантируется обратная совместимость для Rustfmt: отформатированный сегодня код останется неизменным в будущем (только с опциями использованными по-умолчанию), и несёт практическую ценность при использовании с системами непрерывной интеграции (CI; cargo fmt --check). Установить Rustfmt можно следующим образом: rustup component add rustfmt, использовать: cargo fmt.

Поддержка IDE - одна из наиболее востребованных возможностей в Rust. Работы над поддержкой IDE ещё не закончены, но на данный момент уже существуют несколько высококачественных опций:

Tool lints

В Rust 1.30 были стабилизированы атрибуты инструментов, такие как #[rustfmt::skip]. В Rust 1.31 стабилизированы «анализаторы инструментов» («tool lints») наподобие #[allow(clippy::bool_comparison)], у них появилось своё пространство имён и теперь ясно к какому инструменту они относятся. Старые проверки Clippy теперь можно писать следующим образом, вам больше не нужен атрибут cfg_attr:

// old
#![cfg_attr(clippy, bool_comparison)]

// new
#![allow(clippy::bool_comparison)]

Документация

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

Рабочие группы

В этом году было объявлено о создании четырёх рабочих групп в следующих областях:

  • Сетевые сервисы
  • Приложения командной строки
  • WebAssembly
  • Встраиваемые устройства

Каждая из этих групп очень много работала над тем, чтобы сделать Rust лучше в своей области. Некоторые основные моменты:

  • Группа сетевых сервисов работает над интерфейсом Futures и async/await, который уже будет доступен в скором времени.
  • Группа командной строки работает над библиотеками и документацией для создания ещё лучших приложений для командной строки.
  • Группа WebAssembly выпустила огромное количество инструментария для использования Rust с wasm.
  • Группа встраиваемых устройств добилась поддержки разработки ARM на стабильной версии Rust.

Новый сайт

Основной сайт получил новый дизайн.

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

Добавлено множество реализаций From:

  • u8 теперь реализует From<NonZeroU8>, то же самое для других числовых типов и их NonZero-эквивалентов
  • Option<&T> реализует From<&Option<T>>, аналогично для &mut

Были стабилизированы следующие функции:

  • slice::align_to и её изменяемый аналог
  • slice::chunks_exact и её изменяемый и r аналоги (такие как slice::rchunks_exact_mut) во всех комбинациях

Подробный список изменений можно посмотреть здесь.

Cargo

Cargo теперь загружает пакеты параллельно, используя HTTP/2. В связи с тем, что extern crate практически больше не требуется, было бы неудобно использовать пакет через extern crate foo as bar; Это можно сделать в Cargo.toml следующим образом:

[dependencies]
baz = { version = "0.1", package = "foo" }

или

[dependencies.baz]
version = "0.1"
package = "foo"

В примере выше, пакет foo теперь может быть использован через baz.

Подробный список изменений можно посмотреть здесь.

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

★★★★★

Проверено: Shaman007 ()

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

и в Rust нет линейных тмппов, кстати

Не позорься.

https://en.wikipedia.org/wiki/Substructural_type_system

Ссылки на предыдущие реализации borrow checker есть?

https://en.wikipedia.org/wiki/Mercury_(programming_language)

что такое linearity checker?

Часть тайп чекера.

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

Загуглил цитату

Как именно система типов гарантирует корректность логики

Бегло увидел много интересного. Вы бы гуглили. Можно начать с «изоморфизм Карри — Ховарда».

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

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

Согласно соответствию Карри-Говарда типы соответствуют логическим утверждениям, а программамы, соответствующие типам — доказательствам их верности (в интуиционистской логике построение объекта, соответствующего утверждению, есть доказательство). Далее все зависит только от выразительности системы типов, то есть от того, насколько точно можно описать необходимые инварианты в системе типов. Ну а имея достаточно выразительную систему типов, можно доказывать практически все, на манер того, как это делается в coq, вот примеры

https://github.com/FStarLang/FStar/blob/master/examples/algorithms/QuickSort....

https://github.com/FStarLang/FStar/blob/master/examples/crypto/MAC.fst

https://mitpress.mit.edu/books/little-typer

https://softwarefoundations.cis.upenn.edu/

https://www.manning.com/books/type-driven-development-with-idris

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

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

Вот у меня все последние утечки были в библиотечном коде, как предполагается ловить утечки в чужом (иногда закрытом) коде юнит тестами?

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

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

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

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

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

Пишу

val sum : x:int -> y:int -> Tot (res:int{res = x + y})
let sum x y = x - y

Не работает, не компилируется.

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

и в Rust нет линейных тмппов, кстати

Не позорься.

Ой, позор-то какой, как теперь жыть. Позор - это думать, что в Rust - линейные типы и приводить в доказательство этого ссылку на более широкий класс систем типов.

Ссылки на предыдущие реализации borrow checker есть?

https://en.wikipedia.org/wiki/Mercury_(programming_language)

Я просили ссылку на реализацию borrow checker, а не название языка.

что такое linearity checker?

Часть тайп чекера.

В каком языке? Если в Rust, то где описана эта часть?

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

Тогда уж:

BOOST_STRONG_TYPEDEF(std::string, A)
BOOST_STRONG_TYPEDEF(std::string, B)
BOOST_STRONG_TYPEDEF(double, C)
using Event = std::variant <A,B,C>;

Если на буст аллергия, можно использовать любую другую реализацию NamedType или аналога. Или набросать свою.

PS: и это уже не к энумам вопрос, а к strong typedefs.

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

В каком языке?

В любом языке с линейными типами.

https://github.com/pikatchu/LinearML/blob/master/compiler/linearCheck.ml

Я просили ссылку на реализацию borrow checker

Чем borrow checker отличается от любой другой реализации проверки афинных типов?

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

Чем borrow checker отличается от любой другой реализации проверки афинных типов?

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

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

Чем borrow checker отличается от любой другой реализации проверки афинных типов?

Чем borrow checker отличается от любой другой реализации проверки афинных типов?

Тем, что он не является проверкой афинных типов.

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

Это тоже можно отловить типами, но нужны зависимые типы, или с этими сильно извратиться. Завести тип «отрицательное число», завести через интерфейсы связи между теми величинами в формуле.

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

С линейными или аффинными типами ты можешь лишь передавать значение как есть.

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

http://users.eecs.northwestern.edu/~jesse/pubs/alms/tovpucella-alms.pdf

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

То, что создание ссылки не потребляет значение, это очевидный костыль, но едва ли принципиальное отличие.

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

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

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

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

Согласно соответствию Карри-Говарда [...]

Всё это здорово, но ещё есть Blum size theorem: длина программы на total programming language (язык, который гарантирует существование результата вычислений для любых входных данных) может превосходить длину программы на обычном языке в любое количество раз. https://existentialtype.wordpress.com/2014/03/20/old-neglected-theorems-are-s...

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

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

длина программы на total programming language (язык, который гарантирует существование результата вычислений для любых входных данных) может превосходить длину программы на обычном языке в любое количество раз. https://existentialtype.wordpress.com/2014/03/20/old-neglected-theorems-are-s...

«This may seem appealing, until one considers that the upper bound on the time to termination can be quite large, so large that some terminating programs might just as well diverge as far as we humans are concerned».

П - практичность.

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

не может проверить exhaustiveness значений, только тагов

Это, типа, предупредить, если проверены не все значения enum-а? Так это для обычных switch-case проверяет -Wswitch (или, по желанию, -Wswitch-enum).

Или надо обязательно как в C++ P0095R1?

inspect(i) {
  0 => std::cout << "I can't say I'm positive or negative on this syntax." << std::endl;
  6 => std::cout << "Perfect!" << std::endl;
  _ => std::cout << "I don't know what to do with this." << std::endl;
}

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

длина программы на total programming language может превосходить длину программы на обычном языке в любое количество раз

Используешь total программы для доказательства утверждений о non-total программах, как это сделано в F* (там для effectful computations и верификации их свойств используются монады хоара/дийкстры, что позволяет делать утверждения в типе вычисления в духе триплетов хоара, добавляя total evidences по необходимости для доказательства) и скорей всего idris. Для доказательств утверждений тебе не нужны программы в несколько квадриллионов строк.

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

Так это для обычных switch-case проверяет

Для визитора не проверяет.

Или надо обязательно как в C++ P0095R1?

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

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

Перепутай его, и функция будет соответствовать своему типу.

Т.е. все ошибки не отловить, и надо либо безошибочно писать (хотя бы типы), либо писать тесты?

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

и надо либо безошибочно писать (хотя бы типы), либо писать тесты?

Тесты можно писать ошибочно?

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

Тест проверяет частный случай и ничего не говорит о свойствах кода в целом, ошибка в тесте ведет к реакции «wtf, в этом кейсе так и задумано, или это косяк».

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

Для визитора не проверяет.

Так визит сам проверяет же! Или я не понимаю о чём речь. Можно пример кода на С++, который не должен компилиться, но компилится?

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

Лично у меня к ним довольно смешанное отношение, но, думаю, в конце концов добавят. Как когда-то добавили лямбды. Это ведь не принципиальная проблема, при желании сделать можно, было бы желание. Просто они прорабатывают детали для стандарта.

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

Тесты можно писать ошибочно?

Можно. Поэтому часто количество тестов избыточно. Равно как и тип для сложения тогда надо описывать полностью: с коммутативностью, ассоциативностью, дистрибутивностью, нулём.... И это только для сложения!

А как описать функцию Аккермана, чтобы верными были как решение в лоб, так и полутабличный расчёт? А ряды... И это всё для гарантии?

А булевы свёртки или преобразования интегралов тоже пройдут?

Тут недавно вспоминали букву П...

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

Можно. Поэтому часто количество тестов избыточно. Равно как и тип для сложения тогда надо описывать полностью: с коммутативностью, ассоциативностью, дистрибутивностью, нулём.... И это только для сложения!

Да, именно так, если тебе нужно доказывать свойства функций, использующих сложение, тебе нужно ввести свойства сложения хотя бы в виде аксиом.

А как описать функцию Аккермана

Берешь и описываешь

Не очень только понимаю, причем тут тесты. Если функция проходит кейсы `add 1 1 = 2` `add 1 2 = 3` `add 1 3 = 3`, она корректна? Или тесты врут? Тесты избыточны?

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

Понял. Согласен, в идеальном мире наверное действительно можно заколбасить все типами. А разве по трудоемкость это не будет слишком сложно?

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

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

Не возьмусь утверждать однозначно про типы, т.к. недостаточно разбирась. Подозреваю, что:

- Описывая типы ты можешь находиться в том же «контексте», что и при написании кода (может глаз замылится)
- Корректно и полностью описать типы может быть заметно сложнее чем написать тесты.

Не возьмусь спорить, т.к. не владею вопросом. Есть общее ощущения что дурят и примерно где именно :). Возможно ошибаюсь.

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

Есть общее ощущения что дурят и примерно где именно

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

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

Понял. Согласен, в идеальном мире наверное действительно можно заколбасить все типами. А разве по трудоемкость это не будет слишком сложно?

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

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

На практике 1) можно просто писать обычный типизированный код с эффектами, бесконечными циклами и.т.д., а тотальные функции использовать только для доказательств и 2) я вообще не понял, как мы ушли в какие-то дебри верификации, если код вроде

module Nat = struct
 type t
 val of_int : int -> t option
end

val some_function_over_nats : Nat.t -> Nat.t -> ...

Уже лучше тестов, проверяющих, что функция получает натуральные числа (или список, непустой список, сбалансированное дерево, открытый дескриптор и.т.д.)

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

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

С логикой беда?

что если тесты (свидетельства корректности частных случаев) можно заменить типами (свидетельствами корректности в общем случае), это нужно сделать

вплоть до полной ненужности тестов в каких-нибудь языках с dependent types

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

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

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

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

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

Создают тестовую прграмму на каждый чих?

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

Не, речь шла о тестах на каждый чих, от который не спасают типы.

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

Никто не тянул тебя за язык говорить о «полной ненужности».

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

Альзо я не говорил, что тесты не нужны

Freyr69 ★★★ ()