Выпуск Rust 1.30

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

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

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

1
$ rustup update stable

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

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

Rust 1.30 — выдающийся выпуск с рядом важных нововведений. Но уже в понедельник в официальном блоге будет опубликована просьба проверить бета-версию Rust 1.31, которая станет первым релизом «Rust 2018». Дополнительную информацию об этом вы найдёте в нашей предыдущей публикации «What is Rust 2018».

Процедурные макросы

Ещё в Rust 1.15 мы добавили возможность определять «пользовательские derive-макросы». Например, с помощью serde_derive, вы можете объявить:

1
2
3
4
#[derive(Serialize, Deserialize, Debug)]
struct Pet {
    name: String,
}

И конвертировать Pet в JSON и обратно в структуру, используя serde_json. Это возможно благодаря автоматическому выводу типажей Serialize и Deserialize с помощью процедурных макросов в serde_derive.

Rust 1.30 расширяет функционал процедурных макросов, добавляя возможность определять ещё два других типа макросов: «атрибутные процедурные макросы» и «функциональные процедурные макросы».

Атрибутные макросы подобны derive-макросам для автоматического вывода, но вместо генерации кода только для атрибута #[derive], они позволяют пользователям создавать собственные новые атрибуты. Это делает их более гибкими: derive-макросы работают только для структур и перечислений, но атрибуты могут применяться и к другим объектам, таким как функции. Например, атрибутные макросы позволят вам при использовании веб-фреймворка делать следующее:

1
2
#[route(GET, "/")]
fn index() {

Этот атрибут #[route] будет определён в самом фреймворке как процедурный макрос. Его сигнатура будет выглядеть так:

1
2
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {

Здесь у нас имеется два входных параметра типа TokenStream: первый — для содержимого самого атрибута, то есть это параметры GET, "/". Второй — это тело того объекта, к которому применён атрибут. В нашем случае — это fn index() {} и остальная часть тела функции.

Функциональные макросы определяют такие макросы, использование которых выглядит как вызов функции. Например, макрос sql!:

1
let sql = sql!(SELECT * FROM posts WHERE id=1);

Этот макрос внутри себя будет разбирать SQL-выражения и проверять их на синтаксическую корректность. Подобный макрос должен быть объявлен следующим образом:

1
2
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {

Это похоже на сигнатуру derive-макроса: мы получаем токены, которые находятся внутри скобок, и возвращаем сгенерированный по ним код.

Макросы и use

Теперь можно импортировать макросы в область видимости с помощью ключевого слова use. Например, для использования макроса json из пакета serde-json, раньше использовалась запись:

1
2
3
4
5
6
7
8
9
10
11
#[macro_use]
extern crate serde_json;

let john = json!({
    "name": "John Doe",
    "age": 43,
    "phones": [
        "+44 1234567",
        "+44 2345678"
    ]
});

А теперь вы должны будете написать:

1
2
3
4
5
6
7
8
9
10
11
12
extern crate serde_json;

use serde_json::json;

let john = json!({
    "name": "John Doe",
    "age": 43,
    "phones": [
        "+44 1234567",
        "+44 2345678"
    ]
});

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

Наконец, стабилизирован пакет proc_macro, который даёт API, необходимый для написания процедурных макросов. В нем также значительно улучшили API для обработки ошибок, и такие пакеты, как syn и quote уже используют его. Например, раньше:

#[derive(Serialize)]
struct Demo {
    ok: String,
    bad: std::thread::Thread,
}

приводило к такой ошибке:

1
2
3
4
5
error[E0277]: the trait bound `std::thread::Thread: _IMPL_SERIALIZE_FOR_Demo::_serde::Serialize` is not satisfied
 --> src/main.rs:3:10
  |
3 | #[derive(Serialize)]
  |          ^^^^^^^^^ the trait `_IMPL_SERIALIZE_FOR_Demo::_serde::Serialize` is not implemented for `std::thread::Thread`

Теперь же будет выдано:

1
2
3
4
5
error[E0277]: the trait bound `std::thread::Thread: serde::Serialize` is not satisfied
 --> src/main.rs:7:5
  |
7 |     bad: std::thread::Thread,
  |     ^^^ the trait `serde::Serialize` is not implemented for `std::thread::Thread`

Улучшение системы модулей

Система модулей долгое время становилась больным местом для новичков в Rust’е; некоторые из её правил оказывались неудобными на практике. Настоящие изменения являются первым шагом, который мы предпринимаем на пути упрощения системы модулей.

В дополнении к вышеупомянутому изменению для макросов, есть два новых улучшения в использовании use. Во-первых, внешние пакеты теперь добавляются в prelude, то есть:

1
2
3
4
5
// было
let json = ::serde_json::from_str("...");

// стало
let json = serde_json::from_str("...");

Подвох в том, что старый стиль не всегда был нужен из-за особенностей работы системы модулей Rust:

extern crate serde_json;

fn main() {
    // это прекрасно работает; мы находимся в корне пакета, поэтому `serde_json`
    // здесь в области видимости
    let json = serde_json::from_str("...");
}

mod foo {
    fn bar() {
        // это не работает; мы внутри пространства имён `foo`, и `serde_json`
        // здесь не объявлен
        let json = serde_json::from_str("...");

    }

    // одно решение - это импортировать его внутрь модуля с помощью `use`
    use serde_json;

    fn baz() {
        // другое решение - это использовать `::serde_json`, когда указывается
        // абсолютный путь, вместо относительного
        let json = ::serde_json::from_str("...");
    }
}

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

Наконец, use стал поддерживать импорт элементов в текущую область видимости с путями, которые начинаются на crate:

1
2
3
4
5
6
7
8
9
10
11
12
13
mod foo {
    pub fn bar() {
        // ...
    }
}

// было
use ::foo::bar;
// или
use foo::bar;

// стало
use crate::foo::bar;

Ключевое слово crate в начале пути указывает, что путь будет начинаться от корня пакета. Раньше пути, указанные в строке импорта use, всегда указывались относительно корня пакета, но пути в остальном коде, напрямую ссылающиеся на элементы, указывались относительно текущего модуля, что приводило к противоречивому поведению путей:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mod foo {
    pub fn bar() {
        // ...
    }
}

mod baz {
    pub fn qux() {
        // было
        ::foo::bar();
        // не работает, в отличии от `use`:
        // foo::bar();

        // стало
        crate::foo::bar();
    }
}

Как только новый стиль станет широко использоваться, то он, мы надеемся, сделает абсолютные пути более ясными, без необходимости использовать уродливый префикс ::.

Все эти изменения в совокупности упрощают понимание того, как разрешаются пути. В любом месте, где вы видите путь a::b::c, кроме оператора use, вы можете спросить:

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

Сырые идентификаторы

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

1
2
3
4
5
6
7
8
9
10
// определение локальной переменной с именем `for`
let r#for = true;

// определение функции с именем `for`
fn r#for() {
    // ...
}

// вызов той функции 
r#for();

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

Приложения без стандартной библиотеки

Ещё в Rust 1.6 мы объявили о стабилизации no_std и libcore для создания проектов без стандартной библиотеки. Однако, с одним уточнением: можно было создавать только библиотеки, но не приложения.

В Rust 1.30 можно использовать атрибут #[panic_handler] для самостоятельной реализации паники. Это означает, что теперь можно создавать приложения, а не только библиотеки, которые не используют стандартную библиотеку.

Другое

И последнее: в макросах теперь можно сопоставлять модификаторы области видимости, такие как pub, с помощью спецификатора vis. Дополнительно, «инструментальные атрибуты», такие как #[rustfmt::skip], теперь стабилизированы. Правда для использования с инструментами статического анализа, наподобие #[allow(clippy::something)], они ещё не стабильны.

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

Стабилизация стандартной библиотеки

В этом выпуске были стабилизированы следующие API:

Кроме того, стандартная библиотека уже давно имеет функции для удаления пробелов с одной стороны некоторого текста, такие как trim_left. Однако, для RTL-языков значение «справа» и «слева» тут приводят к путанице. Поэтому мы вводим новые имена для этих функций:

Мы планируем объявить устаревшими старые имена (но не удалить, конечно) в Rust 1.33.

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

Улучшения в Cargo

Самое большое улучшение Cargo в этом выпуске заключается в том, что теперь у нас есть индикатор выполнения!

demo gif

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

Разработчики 1.30.0

Множество людей совместно создавало Rust 1.30. Мы не смогли бы завершить работу без участия каждого из вас. Спасибо!


Обсуждение — на форуме или /r/rustycrate.