Команда Rust рада сообщить о выпуске новой версии — 1.51.0. Rust — это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.


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


rustup update stable

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


Что было стабилизировано в 1.51.0


Этот выпуск представляет одно из наиболее крупных дополнений языка Rust и Cargo за долгое время, включающее в себя стабилизацию константных обобщений в минимально полезном варианте и новый распознаватель функциональности в Cargo. Давайте посмотрим подробнее!


Константные обобщения (Const Generics MVP)


До этого выпуска Rust позволял параметризовать типы по времени жизни или по типам. Например, если бы мы хотели получить struct, которая является обобщённой для массива, мы бы написали следующее:


struct FixedArray<T> {
              // ^^^ Определение обобщённого типа.
    list: [T; 32]
        // ^ Где мы использовали его.
}

Если затем мы используем FixedArray<u8>, компилятор создаст мономорфизированную версию FixedArray, которая выглядит так:


struct FixedArray<u8> {
    list: [u8; 32]
}

Этот полезный функционал позволяет писать повторно используемый код без дополнительных затрат во время выполнения. Однако до этого выпуска у нас не было возможности легко объединять значения таких типов. Это наиболее заметно в массивах, где длина указывается в определении типа ([T; N]). Теперь в версии 1.51.0 вы можете писать код, который будет обобщённым для значений любого числа, типа bool или char! (Использование значений struct и enum по-прежнему не стабилизировано.)


Это изменение позволяет создать массив, обобщённый по типу и длине. Давайте посмотрим на пример определения и то, как его можно использовать.


struct Array<T, const LENGTH: usize> {
    //          ^^^^^^^^^^^^^^^^^^^ Определение константного обобщения.
    list: [T; LENGTH]
    //        ^^^^^^ Мы использовали его здесь.
}

Теперь если мы используем Array<u8, 32>, компилятор создаст мономорфизированную версию Array, которая выглядит так:


struct Array<u8, 32> {
    list: [u8; 32]
}

Константные обобщения добавляют важный новый инструмент для разработчиков библиотек, чтобы создавать новые, мощные и безопасных API во время компиляции. Если вы хотите узнать больше о константных обобщениях, можете почитать статью в блоге «Const Generics MVP Hits Beta» для получения дополнительной информации об этой функции и её текущих ограничениях. Нам не терпится увидеть, какие новые библиотеки и API вы создадите!


Стабилизация array::IntoIter


Как часть стабилизации константных обобщений, мы также стабилизировали использующее их новое API — std::array::IntoIter. IntoIter позволяет вам создать поверх массива итератор по значению. Ранее не было удобного способа итерироваться по самим значениям, только по ссылкам.


fn main() {
  let array = [1, 2, 3, 4, 5];

  // Раньше
  for item in array.iter().copied() {
      println!("{}", item);
  }

  // Теперь
  for item in std::array::IntoIter::new(array) {
      println!("{}", item);
  }
}

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


Новый распознаватель функциональности Cargo


Управление зависимостями — сложная задача, и одна из самых сложных её частей — выбор версии зависимости, когда от неё зависят два разных пакета. Здесь учитывается не только номер версии, но и то, какая функциональность была включена или выключена для пакета. По умолчанию Cargo объединяет функциональные флаги (features) для одного пакета, если он встречается в графе зависимостей несколько раз.


Например, у вас есть зависимость foo с функциональными флагами A и B, которые используются пакетами bar and baz, но bar зависит от foo+A, а baz — от foo+B. Cargo объединит оба флага и соберёт foo как foo+AB. Выгода здесь в том, что foo будет собран только один раз и далее будет использован и для bar, и для baz.


Но у этого решения есть и обратная сторона. Что, если функциональный флаг подключён как build-зависимость, но не совместим с конечной целью сборки?


Общим примером этого из экосистемы может служить опциональная функциональность std во многих #![no_std] пакетах, которая позволяет этим пакетам предоставить дополнительную функциональность, если она включена. Теперь представим, что вы хотите использовать #![no_std] версию foo в вашей #![no_std] программе и использовать foo во время сборки в build.rs. Так как во время сборки вы зависите от foo+std, то и ваша программа тоже зависит от foo+std, а значит более не может быть скомпилирована, так как std не доступна для вашей целевой платформы.


Это была давняя проблема в Cargo, и с этим выпуском появилась новая опция resolver в вашем Cargo.toml, где вы можете установить resolver="2", чтобы попробовать новый подход к разрешению функциональных флагов. Вы можете ознакомиться с RFC 2957 для получения подробного описания поведения, которое можно резюмировать следующим образом.


  • Dev dependencies — когда пакет используется совместно как обычная зависимость и dev, возможности dev-зависимости включаются только в том случае, если текущая сборка включает dev-зависимости.
  • Host Dependencies — когда пакет совместно используется как обычная зависимость и зависимость сборки или процедурный макрос, features для нормальной зависимости сохраняются независимо от зависимости сборки или процедурного макроса.
  • Target dependencies — когда у пакета включены зависимые от платформы features, и он присутствует в графе сборки несколько раз, будут включены только features, подходящие текущей платформе сборки.

Хотя это может привести к компиляции некоторых пакетов более одного раза, это должно обеспечить гораздо более интуитивный опыт разработки при использовании функций с Cargo. Если вы хотите узнать больше, вы также можете прочитать раздел «Feature Resolver» в Cargo Book для получения дополнительной информации. Мы хотели бы поблагодарить команду Cargo и всех участников за их тяжёлую работу по разработке и внедрению нового механизма!


[package]
resolver = "2"
# Или если вы используете workspace
[workspace]
resolver = "2"

Разделение отладочной информации


Хоть это и нечасто освещается в релизах, команда Rust постоянно работает над сокращением времени компиляции. В этом выпуске вносится самое крупное улучшение за долгое время для Rust на macOS. Отладочная информация исходного кода содержится в собранном бинарнике, и за счет этого программа может дать больше информации о том, что происходит во время исполнения. Раньше в macOS отладочная информация собиралась в единую директорию .dSYM при помощи утилиты dsymutil, что могло занимать много времени и дискового пространства.


Сбор всей отладочной информации в эту директорию помогал найти её во время выполнения, особенно если бинарник перемещался. Однако у такого решения есть и обратная сторона: если вы сделали небольшое изменение в вашей программе, то dsymutil необходимо запустить над всем собранным бинарником, чтобы собрать директорию .dSYM. Иногда это могло сильно увеличить время сборки, особенно для крупных проектов, поскольку надо перебирать все зависимости, но это важный шаг, без которого стандартная библиотека Rust не знает, как загружать отладочную информацию на macOS.


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


Вы можете включить новое поведение, установив флаг -Csplit-debuginfo=unpacked при запуске rustc или задав опцию split-debuginfo в unpacked раздела [profile] в Cargo. С опцией "unpacked" rustc будет оставлять объектные файлы (.o) в директории сборки вместо их удаления и пропустит запуск dsymutil. Поддержка бэктрейсов Rust достаточно умна, чтобы понять, как найти эти .o файлы. Такие инструменты, как lldb, также знают, как это делается. Это должно работать до тех пор, пока вам не понадобится переместить бинарники в другое место и сохранить отладочную информацию.


[profile.dev]
split-debuginfo = "unpacked"

Стабилизированные API


Итого: в этом выпуске было стабилизировано 18 новых методов для разных типов, например slice и Peekable. Одним из примечательных дополнений является стабилизация ptr::addr_of! и ptr::addr_of_mut!, которая позволяет вам создавать сырые указатели для полей без выравнивания. Ранее это было невозможно, так как Rust требовал, чтобы &/&mut были выровнены и указывали на инициализированные данные. Из-за этого преобразование &addr as *const _ приводило к неопределённому поведению, так как &addr должно быть выровнено. Теперь эти два макроса позволяют вам безопасно создать невыровненные указатели.


use std::ptr;

#[repr(packed)]
struct Packed {
    f1: u8,
    f2: u16,
}

let packed = Packed { f1: 1, f2: 2 };
// `&packed.f2` будет создана ссылка на невыровненную память, таким образом это неопределённое поведение!
let raw_f2 = ptr::addr_of!(packed.f2);
assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);

Следующие методы были стабилизированы:



Другие изменения


Синтаксис, пакетный менеджер Cargo и анализатор Clippy также претерпели некоторые изменения.


Участники 1.51.0


Множество людей собрались вместе, чтобы создать Rust 1.51.0. Мы не смогли бы сделать это без всех вас. Спасибо!


От переводчиков


С любыми вопросами по языку Rust вам смогут помочь в русскоязычном Телеграм-чате или же в аналогичном чате для новичковых вопросов. Если у вас есть вопросы по переводам или хотите помогать с ними, то обращайтесь в чат переводчиков.
Также можете поддержать нас на OpenCollective.


Данную статью совместными усилиями перевели andreevlex, TelegaOvoshey, blandger, nlinker и funkill.