- PVSM.RU - https://www.pvsm.ru -
Команда разработчиков 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, возможно, самая богатая нововведениями со времен выпуска Rust 1.0. Давайте их рассмотрим!
Почти 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%. В будущем улучшим ещё!
И наконец, одно очень простое улучшение: теперь у 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,455i128: −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 теперь должен обрабатывать зависимости из lock-файлов еще быстрее и интеллектуальнее, а также требовать меньше ручных вызовов cargo update. Исполняемый файл Cargo теперь имеет ту же версию, что и rustc [11].
Подробности смотрите в примечаниях к выпуску [2].
Множество людей участвовало в разработке 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
Нажмите здесь для печати.