Выпуск Rust 1.16

оригинал: The Rust Core Team • перевод: Сергей Веселков • новости • поддержите на Patreon

Команда Rust рада представить выпуск Rust 1.16.0. Rust — это системный язык программирования, нацеленный на безопасность, скорость и параллельное выполнение кода.

Если у вас установлена предыдущая версия Rust, то для обновления достаточно выполнить:

1
$ rustup update stable

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

Что вошло в стабильную версию 1.16.0

Самым большим дополнением в Rust 1.16 является команда cargo check. Эта новая подкоманда в большинстве случаев должна ускорить процесс разработки.

Что она делает? Давайте вернёмся немного назад и поговорим о том, как rustc компилирует ваш код. Компиляция происходит в несколько «проходов». Это значит, что компилятор выполняет множество различных этапов, прежде чем из вашего исходного кода будет создан бинарный файл. Вы можете увидеть каждый их этих этапов (и сколько времени и памяти они занимают) передав компилятору параметр -Z time-passes (только для nightly):

1
2
3
4
5
6
7
8
rustc .\hello.rs -Z time-passes
time: 0.003; rss: 16MB  parsing
time: 0.000; rss: 16MB  recursion limit
time: 0.000; rss: 16MB  crate injection
time: 0.000; rss: 16MB  plugin loading
time: 0.000; rss: 16MB  plugin registration
time: 0.049; rss: 34MB  expansion
<snip>

Их много. Однако вы можете разделить их на два больших этапа. Первый: rustc выполняет все проверки безопасности и корректности синтаксиса. Второй: после того как он убедится, что всё в порядке, он создаст бинарный файл, который вы в конечном итоге запустите.

Как видите, второй этап занимает много времени. И в большинстве случаев в нём нет необходимости. Многие разработчики работают над проектами на Rust примерно так:

  1. Написать немного кода.
  2. Запустить cargo build, чтобы убедиться, что он компилируется.
  3. Повторить первые два шага при необходимости.
  4. Запустить cargo test, чтобы убедиться, что тесты успешно выполняются.
  5. Перейти к первому шагу.

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

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

  первоначальная сборка первоначальная проверка ускорение повторная сборка повторная проверка ускорение
thanks 134.75s 50.88s 2.648 15.97s 2.9s 5.506
cargo 236.78s 148.52s 1.594 64.34s 9.29s 6.925
diesel 15.27s 12.81s 0.015 13.54s 12.3s 1.100

Категория ‘первоначальная’ — это первая сборка после клонирования проекта. Для категории ‘повторная’ добавлялась одна пустая линия в начало файла src\lib.rs, после чего команда выполнялась повторно. Вот почему первоначальная сборка выглядит более печально; помимо самого проекта команда выполняется для всех его зависимостей. Как видите, большой проект с большим количеством зависимостей увидит заметные улучшения, но для маленьких почти нет разницы.

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

Другие улучшения

Для поддержки cargo check, rustc научился генерировать новый вид файлов: .rmeta. Этот файл содержит только метаданные об определённом контейнере. cargo check использует это для ваших зависимостей, чтобы компилятор мог проверить типы и тому подобное. Это также полезно для Rust Language Server и, возможно, других инструментов, которые появятся позже.

Другое важное изменение — удаление давней диагностики: consider using an explicit lifetime parameter. Эта диагностика срабатывала всякий раз, когда у вас неверная аннотация времени жизни, и компилятор думает, что вы имели ввиду что-то другое. Рассмотрим следующий код:

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::str::FromStr;

pub struct Name<'a> {
    name: &'a str,
}

impl<'a> FromStr for Name<'a> {
    type Err = ();

    fn from_str(s: &str) -> Result<Name, ()> {
        Ok(Name { name: s })
    }
}

Здесь Rust не уверен, что делать с временем жизни; этот код не гарантирует, что s будет жить столько же, сколько и Name. При этом s необходим для того чтобы Name был действительным. Давайте попробуем скомпилировать этот код в Rust 1.15.1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
> rustc +1.15.1 foo.rs --crate-type=lib
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in generic type due to conflicting requirements
  --> .\foo.rs:10:5
   |
10 |       fn from_str(s: &str) -> Result<Name, ()> {
   |  _____^ starting here...
11 | |         Ok(Name { name: s })
12 | |     }
   | |_____^ ...ending here
   |
help: consider using an explicit lifetime parameter as shown: fn from_str(s: &'a str) -> Result<Name, ()>
  --> .\foo.rs:10:5
   |
10 |       fn from_str(s: &str) -> Result<Name, ()> {
   |  _____^ starting here...
11 | |         Ok(Name { name: s })
12 | |     }
   | |_____^ ...ending here

Компилятор объясняет проблему и даёт полезный совет. Что же, давайте попробуем им воспользоваться. Изменим код, добавив в него 'a, и попробуем скомпилировать снова.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> rustc +1.15.1 .\foo.rs --crate-type=lib
error[E0308]: method not compatible with trait
  --> .\foo.rs:10:5
   |
10 |       fn from_str(s: &'a str) -> Result<Name, ()> {
   |  _____^ starting here...
11 | |         Ok(Name { name: s })
12 | |     }
   | |_____^ ...ending here: lifetime mismatch
   |
<snip>
help: consider using an explicit lifetime parameter as shown: fn from_str(s: &'a str) -> Result<Name<'a>, ()>
  --> .\foo.rs:10:5
   |
10 |       fn from_str(s: &'a str) -> Result<Name, ()> {
   |  _____^ starting here...
11 | |         Ok(Name { name: s })
12 | |     }
   | |_____^ ...ending here

Он всё ещё не работает. Совет был не такой уж и полезный. Теперь он предлагает добавить ещё одно время жизни, на этот раз для Name. Если мы сделаем это…

1
2
3
4
> rustc +1.15.1 .\foo.rs --crate-type=lib
<snip>
help: consider using an explicit lifetime parameter as shown: fn from_str(s: &'a str) -> Result<Name<'a>, ()>
  --> .\foo.rs:10:5

…это кино я уже смотрел… Компилятор?!

У этой диагностики были благие намерения, но, как видно из этого примера, когда она ошибалась, она ошибалась очень сильно. Иногда она даже предлагала некорректный для Rust синтаксис! Более того, более опытные программисты на Rust на самом деле не нуждаются в этой подсказке, но начинающие принимали их на веру, и уходили в дебри. Из-за этого мы решили полностью удалить это сообщение. Возможно мы вернём его в будущем, но только если мы сможем ограничить ложные срабатывания.

Из других диагностических изменений, предыдущая версия Rust пыталась предложить исправления для опечаток:

1
2
3
let foo = 5;

println!("{}", ffo);

Код выше вызывал следующую ошибку:

1
2
3
4
5
error[E0425]: cannot find value `ffo` in this scope
 --> foo.rs:4:20
  |
4 |     println!("{}", ffo);
  |                    ^^^ did you mean `foo`?

Однако это могло произойти только в определённых обстоятельствах: иногда для локальных переменных и для полей структур. Теперь это работает почти везде. В сочетании с некоторыми другими соответствующими улучшениями это приводит к значительному улучшению этих видов диагностики.

Подробнее смотрите примечания к выпуску.

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

Был стабилизирован 21 новый интерфейс:

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

Теперь все структуры в стандартной библиотеке реализуют Debug.

Улучшено сообщение об ошибке при получении среза &str. Например, для этого кода:

1
&"abcαβγ"[..4]

будет выведена следующая ошибка:

1
2
thread 'str::test_slice_fail_boundary_1' panicked at 'byte index 4 is not
a char boundary; it is inside 'α' (bytes 3..5) of `abcαβγ`'

Части после ; раньше не было.

Подробнее смотрите примечания к выпуску.

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

В дополнении к cargo check, у Cargo и crates.io появилось несколько новых возможностей. Например, cargo build и cargo doc теперь принимают флаг --all для сборки и документировании всех контейнеров в вашем рабочем пространстве.

У Cargo теперь есть флаг --version --verbose, на подобии rustc.

Crates.io теперь может отображать значки TravisCI или AppVeyor для вашего контейнера.

И Cargo и crates.io теперь понимают категории. В отличие от ключевых слов, которые могут быть указаны в свободной форме, категории курируются. Ключевые слова, в отличии от категорий, используются для поиска. Другими словами, категории предназначены для помощи в просмотре каталога, а ключевые слова предназначены для поиска.

Вы можете просматривать контейнеры по категориям здесь.

Подробнее смотрите примечания к выпуску.

Разработчики версии 1.16.0

В последнем выпуске мы представили thanks.rust-lang.org. Мы занимаемся некоторым рефакторингом, чтобы помимо самого Rust добавить и другие проекты. Мы надеемся представить это в следующем выпуске.

137 человек внесли свой вклад в Rust 1.16. Спасибо!