- PVSM.RU - https://www.pvsm.ru -

Выпуск Rust 1.26

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

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

$ rustup update stable

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

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

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

Второе издание книги "Язык программирования Rust"

Почти 18 месяцев Кэрол, Стив и другие работали над полной переработкой книги "Язык программирования Rust". С момента написания первой книги мы узнали много нового о том, как люди изучают Rust, так что новая версия книги теперь лучше во всех отношениях.

Ранее черновик второго издания уже был опубликован на веб-сайте с заявлением о том, что это незавершенная версия. Теперь же в книгу вносятся небольшие финальные правки и она готовится к печати. Так что с этого выпуска мы рекомендуем читать второе издание вместо первого. Вы можете найти его на doc.rust-lang.org [3] или получить локально, выполнив rustup doc --book.

Кстати, о печати: если вам не жалко деревьев, то вы можете заказать бумажную версию книги на NoStarch Press [4]. Содержимое идентично, но вы получите или настоящую физическую копию книги, чтобы поставить ее на полку, или отлично сверстанный PDF. Вся выручка пойдёт на благотворительность.

impl Trait

Наконец-то у нас появился impl Trait! Эта функциональность уже долгое время была очень востребована, ибо она обеспечивает возможность, известную как "экзистенциальные типы". Однако это только звучит страшно, суть идеи проста:

fn foo() -> impl Trait {
    // ...
}

Данная сигнатура типа говорит: "foo — это функция, которая не принимает аргументов и возвращает тип, реализующий типаж Trait." То есть мы не указываем, какой именно тип возврата у foo на самом деле, а указываем только то, что он реализует определенный типаж. Вы можете спросить, чем это отличается от использования типажей-объектов:

fn foo() -> Box<Trait> {
    // ...
}

Это корректный код и такой способ тоже работает, но он хорош не для всех ситуаций. Допустим, у нас есть типаж Trait, который реализован как для i32, так и для f32:

trait Trait {
    fn method(&self);
}

impl Trait for i32 {
    // тут реализация
}

impl Trait for f32 {
    // тут реализация
}

Рассмотрим функцию:

fn foo() -> ? {
    5
}

Мы хотим указать некий тип результата. Раньше был возможен только вариант с типажом-объектом:

fn foo() -> Box<Trait> {
    Box::new(5) as Box<Trait>
}

Но тут используется Box, что влечет выделение памяти в куче. На самом деле мы не хотим возвращать какие-то динамически определяемые данные, поэтому динамическая диспетчеризация тут только вредит. Вместо этого в Rust 1.26 вы можете написать так:

fn foo() -> impl Trait {
    5
}

Это не создает типажа-объекта и больше похоже на то, как если бы мы написали -> i32, но только с упоминанием части, относящейся к Trait. Мы получаем статическую диспетчеризацию, но с возможностью скрыть реальный тип.

Чем это полезно? Одним из хороших применений являются замыкания. Не забывайте, что замыкания в Rust всегда имеют уникальный, незаписываемый тип, который реализует типаж Fn. Это значит, что если ваша функция возвращает замыкание, вы можете сделать так:

// было
fn foo() -> Box<Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

// стало
fn foo() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

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

fn foo() {
    vec![1, 2, 3]
        .into_iter()
        .map(|x| x + 1)
        .filter(|x| x % 2 == 0)
}

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

error[E0308]: mismatched types
 --> src/main.rs:5:5
  |
5 | /     vec![1, 2, 3]
6 | |         .into_iter()
7 | |         .map(|x| x + 1)
8 | |         .filter(|x| x % 2 == 0)
  | |_______________________________^ expected (), found struct `std::iter::Filter`
  |
  = note: expected type `()`
             found type `std::iter::Filter<std::iter::Map<std::vec::IntoIter<{integer}>, [closure@src/main.rs:7:14: 7:23]>, [closure@src/main.rs:8:17: 8:31]>`

Этот 'обнаруженный тип' ('found type') — огромный, потому что каждый адаптер в цепочке добавляет новый тип. Кроме того, у нас тут есть еще и замыкание. Раньше нам приходилось использовать типажи-объекты в подобных случаях, но теперь мы можем просто написать

fn foo() -> impl Iterator<Item = i32> {
    vec![1, 2, 3]
        .into_iter()
        .map(|x| x + 1)
        .filter(|x| x % 2 == 0)
}

и дело сделано. Работать с futures [5] можно так же.

Важно отметить, что иногда типажи-объекты все же нужны. Вы можете использовать impl Trait только если ваша функция возвращает один тип; если вы хотите вернуть несколько, то вам потребуется динамическая диспетчеризация. Например:

fn foo(x: i32) -> Box<Iterator<Item = i32>> {
    let iter = vec![1, 2, 3]
        .into_iter()
        .map(|x| x + 1);

    if x % 2 == 0 {
        Box::new(iter.filter(|x| x % 2 == 0))
    } else {
        Box::new(iter)
    }
}

Здесь итератор фильтра может быть возвращен, а может и нет. Есть два разных типа, которые могут быть возвращены, и поэтому мы должны использовать типаж-объект.

Ну, и последнее: для синтаксической симметрии вы можете использовать impl Trait также и в аргументах. То есть:

// было
fn foo<T: Trait>(x: T) {

// стало
fn foo(x: impl Trait) {

может улучшить вид коротких сигнатур.

Примечание для тех, кто разбирается в теории типов: тут не экзистенциальный, а универсальный тип. Другими словами, impl Trait — универсальный на входе в функцию, но экзистенциальный на выходе.

Улучшены сопоставления в match

Вы когда-нибудь пытались использовать match для ссылки на Option? Например, в подобном коде:

fn hello(arg: &Option<String>) {
    match arg {
        Some(name) => println!("Hello {}!", name),
        None => println!("I don't know who you are."),
    }
}

Если вы попытаетесь его скомпилировать в Rust 1.25, то вы получите такую ошибку:

error[E0658]: non-reference pattern used to match a reference (see issue #42640)
 --> src/main.rs:6:9
  |
6 |         Some(name) => println!("Hello {}!", name),
  |         ^^^^^^^^^^ help: consider using a reference: `&Some(name)`

error[E0658]: non-reference pattern used to match a reference (see issue #42640)
 --> src/main.rs:7:9
  |
7 |         None => println!("I don't know who you are."),
  |         ^^^^ help: consider using a reference: `&None`

Да, конечно. Давайте изменим код:

fn hello(arg: &Option<String>) {
    match arg {
        &Some(name) => println!("Hello {}!", name),
        &None => println!("I don't know who you are."),
    }
}

Мы добавили &, как требовал компилятор. Попробуем скомпилировать снова:

error[E0507]: cannot move out of borrowed content
 --> src/main.rs:6:9
  |
6 |         &Some(name) => println!("Hello {}!", name),
  |         ^^^^^^----^
  |         |     |
  |         |     hint: to prevent move, use `ref name` or `ref mut name`
  |         cannot move out of borrowed content

Да, конечно. Давайте усмирим-таки компилятор, последовав его совету:

fn hello(arg: &Option<String>) {
    match arg {
        &Some(ref name) => println!("Hello {}!", name),
        &None => println!("I don't know who you are."),
    }
}

Теперь компиляция пройдет успешно. Нам пришлось добавить два & и один ref. Но что особенно важно, ничто из этого не было по-настоящему полезным нам, как программистам. Конечно, сначала мы забыли &, но имеет ли это значение? Нам потребовалось добавить ref чтобы получить ссылку на значение, сохраненное внутри Option, но мы и не могли сделать ничего другого, кроме как получить ссылку, так как мы не можем переместить значение за &T.

Итак, начиная с Rust 1.26, первоначальный код без & и ref будет просто компилироваться и делать именно то, что вы ожидаете. Короче говоря, компилятор будет автоматически ссылаться или разыменовывать ссылки в конструкции match. Поэтому, когда мы говорим

    match arg {
        Some(name) => println!("Hello {}!", name),

компилятор автоматически обратится к Some по ссылке, и поскольку это будет заимствование, name свяжется со значением как ref name, тоже автоматически. Если бы мы изменяли значение:

fn hello(arg: &mut Option<String>) {
    match arg {
        Some(name) => name.push_str(", world"),
        None => (),
    }
}

компилятор бы автоматически выполнил изменяемое заимствование, и name оказалось бы связано со значением как ref mut.

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

main может возвращать Result

К слову о досадном рутинном коде: поскольку Rust использует тип Result для возврата ошибок и ? для упрощения их обработки, общей болевой точкой новичков в Rust становится попытка использовать ? в main:

use std::fs::File;

fn main() {
    let f = File::open("bar.txt")?;
}

Это порождает ошибку вроде "error[E0277]: the ? operator can only be used in a function that returns Result". Которая многих людей вынуждает писать подобный код [6]:

fn run(config: Config) -> Result<(), Box<Error>> {
    // ...
}

fn main() {
    // ...

    if let Err(e) = run(config) {
        println!("Application error: {}", e);

        process::exit(1);
    }
}

Наша функция run содержит всю реальную логику, а main вызывает run, проверяет, произошла ли ошибка и завершает работу. Нам нужна эта вторая функция только потому, что main не может вернуть Result, но мы бы хотели использовать ? в своей логике.

В Rust 1.26 вы теперь можете объявить main, который возвращает Result:

use std::fs::File;

fn main() -> Result<(), std::io::Error> {
    let f = File::open("bar.txt")?;

    Ok(())
}

Теперь это работает как надо! Если main вернет ошибку, это приведет к завершению с кодом ошибки и печати отладочной информации об ошибке.

Закрытые диапазоны с ..=

Еще задолго до Rust 1.0, вы могли создавать полуоткрытые диапазоны с .., например:

for i in 1..3 {
    println!("i: {}", i);
}

Этот код напечатает i: 1, а затем i: 2. В Rust 1.26 теперь вы можете создать закрытый диапазон, например:

for i in 1..=3 {
    println!("i: {}", i);
}

Этот код напечатает i: 1, затем i: 2, как предыдущий, но также и i: 3; три — тоже включится в диапазон. Закрытые диапазоны особенно полезны для перебора всех возможных значений. Например, вот удивительная программа на Rust:

fn takes_u8(x: u8) {
    // ...
}

fn main() {
    for i in 0..256 {
        println!("i: {}", i);
        takes_u8(i);
    }
}

Что делает эта программа? Ответ: ничего. Предупреждение, которое мы получаем при компиляции, подсказывает почему:

warning: literal out of range for u8
 --> src/main.rs:6:17
  |
6 |     for i in 0..256 {
  |                 ^^^
  |
  = note: #[warn(overflowing_literals)] on by default

Это правильно, так как i типа u8, который переполняется, и это то же самое, что писать for i in 0..0, поэтому цикл выполняется ноль раз.

Однако с закрытыми диапазонами можно это исправить:

fn takes_u8(x: u8) {
    // ...
}

fn main() {
    for i in 0..=255 {
        println!("i: {}", i);
        takes_u8(i);
    }
}

Этот код выведет 256 строк, которые вы ожидали.

Базовые образцы срезов

Еще одно долгожданное нововведение — "образцы срезов" (slice patterns). Оно позволяет вам сопоставлять с образцом срезы подобно тому, как вы сопоставляете с образцом другие типы данных. Например:

let arr = [1, 2, 3];

match arr {
    [1, _, _] => "начинается с единицы",
    [a, b, c] => "начинается с чего-то другого",
}

В данном случае мы знаем, что arr имеет длину три, и поэтому нам нужны три элемента внутри []. Мы также можем сопоставлять, когда мы не знаем длину:

fn foo(s: &[u8]) {
    match s {
        [a, b] => (),
        [a, b, c] => (),
        _ => (),
    }
}

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

Увеличение скорости

Мы продолжаем улучшать скоростью работы компилятора. Мы обнаружили, что глубоко
вложенные типы в некоторых случаях становились нелинейными, что было исправлено [7]. После этого исправления, вместе с которым было выпущено и много других небольших исправлений, мы наблюдали сокращение времени компиляции вплоть до 12%. В будущем улучшим ещё!

128-разрядные целые числа

И наконец, одно очень простое улучшение: теперь у Rust'а есть 128-разрядные целые числа!

let x: i128 = 0;
let y: u128 = 0;

Они по размеру в два раза больше u64 и поэтому могут содержать большие значения. А именно:

  • u128: 0 — 340,282,366,920,938,463,463,374,607,431,768,211,455
  • i128: −170,141,183,460,469,231,731,687,303,715,884,105,728 — 170,141,183,460,469,231,731,687,303,715,884,105,727

Фух!

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

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

Мы стабилизировали fs::read_to_string [8], который удобнее, чем File::open и io::Read::read_to_string для простого чтения в память всего файла сразу:

use std::fs;
use std::net::SocketAddr;

let foo: SocketAddr = fs::read_to_string("address.txt")?.parse()?;

Теперь вы можете форматировать вывод шестнадцатеричных чисел с Debug [9]:

assert!(format!("{:02x?}", b"Foo") == "[46, 6f, 6f, 00]")

Завершающие запятые теперь поддерживаются всеми макросами в стандартной библиотеке [10].

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

Улучшения в Cargo

В этом выпуске Cargo не получил значительных изменений функциональности, но получил ряд улучшений стабильности и производительности. Cargo теперь должен обрабатывать зависимости из lock-файлов еще быстрее и интеллектуальнее, а также требовать меньше ручных вызовов cargo update. Исполняемый файл Cargo теперь имеет ту же версию, что и rustc [11].

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

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

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

Спасибо! [12]

Авторы перевода: freecoder_xx [13] и ozkriff [14].

Автор: Александр Мещеряков

Источник [15]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/280081

Ссылки в тексте:

[1] установить его: https://www.rust-lang.org/install.html

[2] подробными примечаниями к выпуску Rust 1.26.0: https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1260-2018-05-10

[3] найти его на doc.rust-lang.org: https://doc.rust-lang.org/book/second-edition/

[4] NoStarch Press: https://www.nostarch.com/Rust

[5] futures: https://crates.io/crates/futures

[6] подобный код: https://doc.rust-lang.org/book/second-edition/ch12-03-improving-error-handling-and-modularity.html#extracting-logic-from-main

[7] было исправлено: https://github.com/rust-lang/rust/pull/48296

[8] fs::read_to_string: https://doc.rust-lang.org/std/fs/fn.read_to_string.html

[9] форматировать вывод шестнадцатеричных чисел с Debug: https://github.com/rust-lang/rust/pull/48978

[10] теперь поддерживаются всеми макросами в стандартной библиотеке: https://github.com/rust-lang/rust/pull/48056

[11] теперь имеет ту же версию, что и rustc: https://github.com/rust-lang/cargo/pull/5083

[12] Спасибо!: https://thanks.rust-lang.org/rust/1.26.0

[13] freecoder_xx: https://habr.com/users/freecoder_xx/

[14] ozkriff: https://habr.com/users/ozkriff/

[15] Источник: https://habr.com/post/358514/?utm_source=habrahabr&utm_medium=rss&utm_campaign=358514