Команда разработчиков Rust рада сообщить о выпуске новой версии Rust: 1.30.0. Rust — это системный язык программирования, нацеленный на безопасность, скорость и параллельное выполнение кода.
Если у вас установлена предыдущая версия Rust с помощью rustup
, то для обновления Rust до версии 1.30.0 вам достаточно выполнить:
$ 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
, вы можете объявить:
#[derive(Serialize, Deserialize, Debug)]
struct Pet {
name: String,
}
И конвертировать Pet
в JSON и обратно в структуру, используя serde_json
. Это возможно благодаря автоматическому выводу типажей Serialize
и Deserialize
с помощью процедурных макросов в serde_derive
.
Rust 1.30 расширяет функционал процедурных макросов, добавляя возможность определять еще два других типа макросов: "атрибутные процедурные макросы" и "функциональные процедурные макросы".
Атрибутные макросы подобны derive-макросам для автоматического вывода, но вместо генерации кода только для атрибута #[derive]
, они позволяют пользователям создавать собственные новые атрибуты. Это делает их более гибкими: derive-макросы работают только для структур и перечислений, но атрибуты могут применяться и к другим объектам, таким как функции. Например, атрибутные макросы позволят вам при использовании веб-фреймворка делать следующее:
#[route(GET, "/")]
fn index() {
Этот атрибут #[route]
будет определен в самом фреймворке как процедурный макрос. Его сигнатура будет выглядеть так:
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
Здесь у нас имеется два входных параметра типа TokenStream
: первый — для содержимого самого атрибута, то есть это параметры GET, "/"
. Второй — это тело того объекта, к которому применен атрибут. В нашем случае — это fn index() {}
и остальная часть тела функции.
Функциональные макросы определяют такие макросы, использование которых выглядит как вызов функции. Например, макрос sql!
:
let sql = sql!(SELECT * FROM posts WHERE id=1);
Этот макрос внутри себя будет разбирать SQL-выражения и проверять их на синтаксическую корректность. Подобный макрос должен быть объявлен следующим образом:
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
Это похоже на сигнатуру derive-макроса: мы получаем токены, которые находятся внутри скобок, и возвращаем сгенерированный по ним код.
Макросы и use
Теперь можно импортировать макросы в область видимости с помощью ключевого слова use. Например, для использования макроса json
из пакета serde-json
, раньше использовалась запись:
#[macro_use]
extern crate serde_json;
let john = json!({
"name": "John Doe",
"age": 43,
"phones": [
"+44 1234567",
"+44 2345678"
]
});
А теперь вы должны будете написать:
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,
}
приводило к такой ошибке:
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`
Теперь же будет выдано:
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, то есть:
// было
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
, то он будет использоваться независимо от положения вызова в иерархии модулей.
mod foo {
pub fn bar() {
// ...
}
}
// было
use ::foo::bar;
// или
use foo::bar;
// стало
use crate::foo::bar;
Ключевое слово crate
в начале пути указывает, что путь будет начинаться от корня пакета. Раньше пути, указанные в строке импорта use
, всегда указывались относительно корня пакета, но пути в остальном коде, напрямую ссылающиеся на элементы, указывались относительно текущего модуля, что приводило к противоречивому поведению путей:
mod foo {
pub fn bar() {
// ...
}
}
mod baz {
pub fn qux() {
// было
::foo::bar();
// не работает, в отличии от `use`:
// foo::bar();
// стало
crate::foo::bar();
}
}
Как только новый стиль станет широко использоваться, то он, мы надеемся, сделает абсолютные пути более ясными, без необходимости использовать уродливый префикс ::
.
Все эти изменения в совокупности упрощают понимание того, как разрешаются пути. В любом месте, где вы видите путь a::b::c
, кроме оператора use
, вы можете спросить:
- Является ли
a
именем пакета? Тогда нужно искатьb::c
внутри него. - Является ли
a
ключевым словомcrate
? Тогда нужно искатьb::c
от корня текущего пакета. - В противном случае, нужно искать
a::b::c
от текущего положения в иерархии модулей.
Старое поведение путей в use
, всегда начинающихся от корня пакета, по-прежнему применимо. Но при единовременном переходе на новый стиль, данные правила будут применяться к путям повсюду единообразно, и вам придется гораздо меньше заботиться об импортах при перемещении кода.
Сырые идентификаторы
Вы можете теперь использовать ключевые слова как идентификаторы, используя следующий новый синтаксис:
// определение локальной переменной с именем `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:
Ipv4Addr::{BROADCAST, LOCALHOST, UNSPECIFIED}
Ipv6Addr::{BROADCAST, LOCALHOST, UNSPECIFIED}
Iterator::find_map
Кроме того, стандартная библиотека уже давно имеет функции для удаления пробелов с одной стороны некоторого текста, такие как trim_left
. Однако, для RTL-языков значение "справа" и "слева" тут приводят к путанице. Поэтому мы вводим новые имена для этих функций:
trim_left
->trim_start
trim_right
->trim_end
trim_left_matches
->trim_start_matches
trim_right_matches
->trim_end_matches
Мы планируем объявить устаревшими старые имена (но не удалить, конечно) в Rust 1.33.
Подробности смотрите в примечаниях к выпуску.
Улучшения в Cargo
Самое большое улучшение Cargo в этом выпуске заключается в том, что теперь у нас есть индикатор выполнения!
Подробности смотрите в примечаниях к выпуску.
Разработчики 1.30.0
Множество людей совместно создавало Rust 1.30. Мы не смогли бы завершить работу без участия каждого из вас. Спасибо!
От переводчика: выражаю отдельную благодарность участникам сообщества Rustycrate и лично vitvakatu и Virtuos86 за помощь с переводом и вычиткой.