Команда Rust рада сообщить о новой версии языка — 1.82.0. Rust — это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.
Если у вас есть предыдущая версия Rust, установленная через rustup
, то для обновления до версии 1.82.0 вам достаточно выполнить команду:
$ rustup update stable
Если у вас ещё не установлен rustup
, вы можете установить его с соответствующей страницы нашего веб-сайта, а также посмотреть подробные примечания к выпуску на GitHub.
Если вы хотите помочь нам протестировать будущие выпуски, вы можете использовать канал beta (rustup default beta
) или nightly (rustup default nightly
). Пожалуйста, сообщайте обо всех встреченных вами ошибках.
Что стабилизировано в 1.82.0
cargo info
Теперь у Cargo есть подкоманда info
для вывода информации о пакете в реестре. Не прошло и десяти лет, как мы выполнили вашу просьбу! За прошедшие годы было написано несколько сторонних расширений, но эта реализация разрабатывалась в рамках cargo-information прежде чем стала частью Cargo.
Как пример, ниже вы видите вывод cargo info cc
:
cc #build-dependencies
A build-time dependency for Cargo build scripts to assist in invoking the native
C compiler to compile native C code into a static archive to be linked into Rust
code.
version: 1.1.23 (latest 1.1.30)
license: MIT OR Apache-2.0
rust-version: 1.63
documentation: https://docs.rs/cc
homepage: https://github.com/rust-lang/cc-rs
repository: https://github.com/rust-lang/cc-rs
crates.io: https://crates.io/crates/cc/1.1.23
features:
jobserver = []
parallel = [dep:libc, dep:jobserver]
note: to see how you depend on cc, run cargo tree --invert --package cc@1.1.23
По умолчанию cargo info
описывает версию пакета из локального Cargo.lock
, если таковой имеется. Также вы можете увидеть и более новую версию cargo info cc@1.1.30
.
Изменение целевых платформ Apple
MacOS на 64-битном ARM теперь на уровне 1
Платформа aarch64-apple-darwin
для macOS на 64-битном ARM (семейство чипов M1 и более молодые Apple silicon CPU) теперь на первом уровне поддержки, что означает высочайшие гарантии правильной работы. Как описано на странице поддержки платформ, каждое изменение в репозитории Rust должно пройти все тесты на каждой платформе первого уровня, прежде чем оно может быть принято. Эта платформа была добавлена на второй уровень поддержки в Rust 1.49, благодаря чему она появилась в rustup
, а текущее изменение ставит её в один ряд с Linux на ARM64 и macOS, Linux или Windows на x86.
Платформа Mac Catalyst теперь на уровне 2
Mac Catalyst — это технология Apple, которая позволяет запускать iOS-приложения нативно на Mac. Это очень удобно при тестировании iOS-специфичного кода. По большей части за счёт этого cargo test --target=aarch64-apple-ios-macabi --target=x86_64-apple-ios-macabi
просто работает (в отличие от обычных целевых платформ iOS, которые должны быть связаны в пакет сторонними инструментами до того, как будут запущены на устройстве или в симуляторе).
Эта целевая платформа теперь на втором уровне и может быть загружена при помощи rustup target add aarch64-apple-ios-macabi x86_64-apple-ios-macabi
, так что сейчас самое время обновить ваши пайплайны, чтобы тестировать код и в iOS-подобном окружении.
Точный захват в синтаксисе use<..>
Rust теперь поддерживает синтаксис use <..>
в пределах конкретных границ impl Trait
для управления тем, какие общие параметры времени жизни будут захвачены.
Типы в возвращаемом значении impl Trait
(RPIT) в Rust захватывают определённые общие параметры. Захват этих параметров позволяет использовать их в скрытых типах, что влияет на анализатор заимствований.
В Rust 2021 и более ранних редакциях параметры времени жизни не отображаются в непрозрачных типах для простых функций, а также для функций и методов со встроенными значениями, если только эти параметры времени жизни не указаны синтаксически в непрозрачном типе. К примеру, это будет ошибкой:
//@ edition: 2021
fn f(x: &()) -> impl Sized { x }
error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds
--> src/main.rs:1:30
|
1 | fn f(x: &()) -> impl Sized { x }
| --- ---------- ^
| | |
| | opaque type defined here
| hidden type `&()` captures the anonymous lifetime defined here
|
help: add a `use<...>` bound to explicitly capture `'_`
|
1 | fn f(x: &()) -> impl Sized + use<'_> { x }
| +++++++++
С новым синтаксисом use<..>
мы можем воспользоваться подсказкой в описании ошибки и исправить код следующим образом:
fn f(x: &()) -> impl Sized + use<'_> { x }
Ранее для корректного исправления этого класса ошибок требовалось определить фиктивный трейт, условно называемый Captures
, и использовать его:
trait Captures<T: ?Sized> {}
impl<T: ?Sized, U: ?Sized> Captures<T> for U {}
fn f(x: &()) -> impl Sized + Captures<&'_ ()> { x }
Это называлось "трюк с Captures
", и теперь в этом больше нет необходимости.
Также существовал менее корректный, но более удобный способ исправить ошибку. Вы могли его встретить под названием "трюк с выживанием". Ранее компилятор даже предлагал сделать это по умолчанию. Суть трюка заключалась в следующем:
fn f(x: &()) -> impl Sized + '_ { x }
В этом простом случае написанное в точности эквивалентно + use<'_>
по причинам, описанным в RFC 3498. Однако в реальных случаях это приводит к чрезмерному ограничению возвращаемого непрозрачного типа, что приводит к проблемам. Например, рассмотрим код, который основан на реальном примере:
struct Ctx<'cx>(&'cx u8);
fn f<'cx, 'a>(
cx: Ctx<'cx>,
x: &'a u8,
) -> impl Iterator<Item = &'a u8> + 'cx {
core::iter::once_with(move || {
eprintln!("LOG: {}", cx.0);
x
})
//~^ ERROR lifetime may not live long enough
}
Мы не можем удалить + 'cx
, поскольку время жизни используется в скрытом типе и поэтому должно быть зафиксировано. Мы также не можем добавить ограничение для 'a: 'cx
, поскольку эти периоды жизни на самом деле не связаны, и в целом неверно, что 'a
переживает 'cx
. Однако если мы напишем вместо этого + use<'cx, 'a>
, то всё сработает как надо, и границы будут корректны.
Для того, что мы стабилизируем сегодня, есть некоторые ограничения. Синтаксис use<..>
в настоящее время не может отображаться в трейтах или в имплементации трейтов (но обратите внимание, что там параметры времени жизни в области видимости уже записаны по умолчанию), и в нём должны быть перечислены все параметры общего типа и const
в области видимости. Мы надеемся, что со временем эти ограничения получится снять.
Обратите внимание, что в Rust 2024 приведённые выше примеры будут "просто работать" без необходимости использования синтаксиса use<..>
(или каких-либо хитростей). Это связано с тем, что в новой редакции непрозрачные типы будут автоматически фиксировать все параметры времени жизни в области видимости. Это лучший вариант по умолчанию, и мы видели много свидетельств того, как это делает код чище. В Rust 2024 синтаксис use<..>
будет служить важным элементом отказа от этого значения по умолчанию.
Для получения более подробной информации о синтаксисе use<..>
, захвате и о том, как это применимо к Rust 2024, смотрите главу RPITT lifetime capture rules руководства по редакциям. Подробнее об общем направлении работы смотрите в нашем недавнем посте в блоге — Changes to impl Trait
in Rust 2024.
Собственный синтаксис для создания сырого указателя
Иногда небезопасный код имеет дело с указателями, которые могут быть висящими, оказаться смещены или указывать на некорректные данные. Чаще всего это встречается в структурах с repr(packed)
. В таком случае важно избегать создания ссылки, так как это может привести к неопределённому поведению. Это означает, что обычные операторы &
и &mut
не могут быть использованы, так как они создают ссылку. Даже если ссылка немедленно преобразуется в сырой указатель, неопределённого поведения всё равно не получится избежать.
Многие годы для этих целей использовались макросы std::ptr::addr_of!
и std::ptr::addr_of_mut!
. Теперь же настало время представить нативный синтаксис для этих операций: addr_of!(expr)
стал &raw const expr
, а addr_of_mut!(expr)
— &raw mut expr
. Например:
#[repr(packed)]
struct Packed {
not_aligned_field: i32,
}
fn main() {
let p = Packed { not_aligned_field: 1_82 };
// Это должно привести к неопределённому поведению!
// Такая запись будет отклонена компилятором
//let ptr = &p.not_aligned_field as *const i32;
// Это старый вариант создания указателя
let ptr = std::ptr::addr_of!(p.not_aligned_field);
// А это — новый
let ptr = &raw const p.not_aligned_field;
// Обращение к указателю не изменилось
// Обратите внимание, что `val = *ptr` будет неопределённым поведением,
// так как указатель не выровнен!
let val = unsafe { ptr.read_unaligned() };
}
Встроенный синтаксис подсказывает, что выражение операнда этих операторов интерпретируется как выражение места. В нём также избегается термин address-of при описании действия по созданию указателя. Указатель — это нечто большее, чем просто адрес, поэтому Rust отказывается от таких терминов, как address-of, которые намекают на (ложную) эквивалентность указателей и адресов.
Безопасный код в unsafe extern
Код на Rust может использовать функции и статические переменные из другого кода. Типы сигнатур этих внешних элементов описываются в блоке extern
. Исторически все элементы вне блоков extern
вызывать небезопасно, но мы не хотим писать unsafe
каждый раз в самом блоке extern
.
Однако если сигнатура в блоке extern
неверна, то использование этого элемента приведёт к неопределённому поведению. Будет ли это ошибкой человека, который написал extern
блок, или человека, который использовал этот элемент?
Мы решили, что человек, пишущий блок extern
, несёт ответственность за то, чтобы все сигнатуры, содержащиеся в нём, были правильными, и поэтому теперь мы разрешаем писать unsafe extern
:
unsafe extern {
pub safe static TAU: f64;
pub safe fn sqrt(x: f64) -> f64;
pub unsafe fn strlen(p: *const u8) -> usize;
}
Одним из преимуществ такого подхода является то, что элементы в блоке unsafe extern
могут быть помечены как безопасные для вызова. В приведённом выше примере мы можем вызвать sqrt
без использования unsafe
. Элементы, которые не помечены ни как safe
, ни как unsafe
, обычно считаются unsafe
.
В будущих выпусках мы будем поощрять использование unsafe extern
со статическими анализаторами кода. Начиная с Rust 2024, использование unsafe extern
станет обязательным.
Более подробную информацию см. в RFC 3484 и главе «Небезопасные extern блоки» руководства по изданию.
Небезопасные атрибуты
Некоторые атрибуты Rust, такие как no_mangle
, могут использоваться для получения неопределённого поведения вне блоков unsafe
. Если бы это был обычный код, мы бы потребовали, чтобы он был помещен в unsafe {}
. Но до сих пор атрибуты не имели сопоставимого синтаксиса, поэтому, чтобы подчеркнуть, что они могут подорвать гарантии безопасности Rust, эти атрибуты считаются небезопасными и должны быть записаны следующим образом:
#[unsafe(no_mangle)]
pub fn my_global_function() { }
Старая форма атрибута (без unsafe
) в настоящее время всё ещё принимается, но может быть подвергнута проверке в будущем и станет серьёзной ошибкой в Rust 2024.
Это влияет на следующие атрибуты:
no_mangle
link_section
export_name
Более подробную информацию см. в главе "Небезопасные атрибуты" руководства по изданию.
Исключение пустых типов при сопоставлении с образцом
Шаблоны, соответствующие пустым (т.е. необитаемым) типам по значению, теперь можно опускать:
use std::convert::Infallible;
pub fn unwrap_without_panic<T>(x: Result<T, Infallible>) -> T {
let Ok(x) = x; // `Err` вариант описывать не требуется
x
}
Это работает с пустыми типами, такими как enum Void {}
, или со структурами и перечислениями с видимым пустым полем и без атрибута #[non_exhaustive]
. Это также будет особенно полезно в сочетании с типом never !
, хотя этот тип в настоящее время ещё нестабилен.
В некоторых случаях, впрочем, пустые шаблоны всё равно должны быть записаны. По причинам, связанным с неинициализированными значениями и небезопасным кодом, пропуск шаблонов не допускается, если доступ к пустому типу осуществляется через ссылку, указатель или поле объединения:
pub fn unwrap_ref_without_panic<T>(x: &Result<T, Infallible>) -> &T {
match x {
Ok(x) => x,
// эта ветвь не может быть исключена, потому что ссылка
Err(infallible) => match *infallible {},
}
}
Чтобы избежать конфликтов с крейтами, которые хотят поддерживать несколько версий Rust, ветви match
с пустыми шаблонами пока не сообщаются как предупреждения о недоступном коде, несмотря на то, что их можно удалить.
NaN и const
Операции над числами с плавающей точкой (f32
и f64
) известны своей тонкостью. Одной из причин этого является существование NaN. NaN — это сокращение от "Not a Number" ("не число"), оно используется, например, для представления результата 0.0 / 0.0
. Тонкой работу с NaN делает тот факт, что существует более одного возможного значения NaN: значение NaN имеет знак, который можно проверить с помощью f.is_sign_positive()
, а также "полезную нагрузку", которую можно извлечь с помощью f.to_bits()
. Оба они, однако, полностью игнорируются ==
(который всегда возвращает false
для NaN). Несмотря на весьма успешные попытки стандартизировать поведение операций с плавающей точкой в различных аппаратных архитектурах, детали того, является NaN положительным или отрицательным и какова его точная полезная нагрузка, различаются в разных архитектурах. Чтобы ещё больше усложнить задачу, Rust и его серверная часть LLVM применяют оптимизацию к операциям с плавающей точкой, когда точный числовой результат гарантированно не изменится, но эти оптимизации могут изменить получаемое значение NaN. Например, f * 1.0
может быть оптимизирован только для f
. Однако если f
является NaN, оптимизация может изменить точный битовый набор результата!
Начиная с этого выпуска Rust стандартизирует набор правил поведения значений NaN. Этот набор не является полностью детерминированным, и это означает, что результат операций, подобных (0.0 / 0.0).is_sign_positive()
, может отличаться в зависимости от аппаратной архитектуры, уровней оптимизации и окружающего кода. Код, который задуман быть полностью переносимым, обязан избегать использования to_bits
и должен использовать f.signum() == 1.0
вместо f.is_sign_positive()
. Однако правила были тщательно подобраны, чтобы по-прежнему позволять использовать в Rust-коде дополнительные методы представления данных, такие как упаковка NaN. Для получения более подробной информации о том, каковы точные правила, ознакомьтесь с документацией.
Поскольку семантика значений NaN установлена, в этом выпуске также разрешено использование операций с плавающей точкой в const fn
. По причинам, описанным выше, такие операции, как (0.0 / 0.0).is_sign_positive()
, могут привести к другому результату при выполнении во время компиляции и во время выполнения. Это не ошибка, и код не должен полагаться на то, что const fn
всегда приводит к одному и тому же результату.
Константы как непосредственные элементы сборки
Операнд сборки const
теперь предоставляет способ использовать целые числа как непосредственные без предварительного сохранения их в регистре. В качестве примера мы реализуем системный вызов для write
вручную:
const WRITE_SYSCALL: c_int = 0x01; // syscall 1 is `write`
const STDOUT_HANDLE: c_int = 0x01; // `stdout` has file handle 1
const MSG: &str = "Hello, world!\n";
let written: usize;
// Сигнатура: `ssize_t write(int fd, const void buf[], size_t count)`
unsafe {
core::arch::asm!(
"mov rax, {SYSCALL} // rax содержит номер системного вызова",
"mov rdi, {OUTPUT} // rdi — `fd` (первый аргумент)",
"mov rdx, {LEN} // rdx — `count` (третий аргумент)",
"syscall // вызов системного вызова",
"mov {written}, rax // сохранение возвращаемого значения",
SYSCALL = const WRITE_SYSCALL,
OUTPUT = const STDOUT_HANDLE,
LEN = const MSG.len(),
in("rsi") MSG.as_ptr(), // rsi - `buf *` (второй аргумент)
written = out(reg) written,
);
}
assert_eq!(written, MSG.len());
Вывод:
Hello, world!
В приведённом выше примере оператор LEN = const MSG.len()
заполняет спецификатор формата LEN
с помощью непосредственного значения, которое принимает значение MSG.len()
. Это можно увидеть в сгенерированной сборке (значение равно 14
):
lea rsi, [rip + .L__unnamed_3]
mov rax, 1 # rax holds the syscall number
mov rdi, 1 # rdi is `fd` (first argument)
mov rdx, 14 # rdx is `count` (third argument)
syscall # invoke the syscall
mov rax, rax # save the return value
Более подробную информацию смотрите по ссылке.
Безопасное разыменование unsafe static
Этот код теперь разрешён:
static mut STATIC_MUT: Type = Type::new();
extern "C" {
static EXTERN_STATIC: Type;
}
fn main() {
let static_mut_ptr = &raw mut STATIC_MUT;
let extern_static_ptr = &raw const EXTERN_STATIC;
}
В контексте выражения STATIC_MUT
и EXTERN_STATIC
являются выражениями места. Ранее проверки безопасности компилятора не знали, что оператор raw ref
на самом деле не влияет на место операнда, и рассматривали его как возможное чтение или запись в указатель. Однако на самом деле никакой опасности нет, поскольку он просто создаёт указатель.
Если это опустить, могут возникнуть проблемы, когда некоторые небезопасные блоки сообщаются как неиспользуемые, если вы отклоняете анализатор unused_unsafe
. Теперь это может быть полезно только в старых версиях. Старайтесь аннотировать эти небезопасные блоки с помощью #[allow(unused_unsafe)]
, если хотите поддерживать несколько версий Rust, как в этом примере:
static mut STATIC_MUT: Type = Type::new();
fn main() {
+ #[allow(unused_unsafe)]
let static_mut_ptr = unsafe { std::ptr::addr_of_mut!(STATIC_MUT) };
}
Ожидается, что будущая версия Rust распространит это на другие выражения, чтобы они были безопасны не только в static
.
Стабилизированные API
std::thread::Builder::spawn_unchecked
std::str::CharIndices::offset
std::option::Option::is_none_or
Iterator::is_sorted
Iterator::is_sorted_by
Iterator::is_sorted_by_key
std::future::Ready::into_inner
std::iter::repeat_n
impl<T: Clone> DoubleEndedIterator for Take<Repeat<T>>
impl<T: Clone> ExactSizeIterator for Take<Repeat<T>>
impl<T: Clone> ExactSizeIterator for Take<RepeatWith<T>>
impl Default for std::collections::binary_heap::Iter
impl Default for std::collections::btree_map::RangeMut
impl Default for std::collections::btree_map::ValuesMut
impl Default for std::collections::vec_deque::Iter
impl Default for std::collections::vec_deque::IterMut
Rc<T>::new_uninit
Rc<T>::assume_init
Rc<[T]>::new_uninit_slice
Rc<[MaybeUninit<T>]>::assume_init
Arc<T>::new_uninit
Arc<T>::assume_init
Arc<[T]>::new_uninit_slice
Arc<[MaybeUninit<T>]>::assume_init
Box<T>::new_uninit
Box<T>::assume_init
Box<[T]>::new_uninit_slice
Box<[MaybeUninit<T>]>::assume_init
core::arch::x86_64::_bextri_u64
core::arch::x86_64::_bextri_u32
core::arch::x86::_mm_broadcastsi128_si256
core::arch::x86::_mm256_stream_load_si256
core::arch::x86::_tzcnt_u16
core::arch::x86::_mm_extracti_si64
core::arch::x86::_mm_inserti_si64
core::arch::x86::_mm_storeu_si16
core::arch::x86::_mm_storeu_si32
core::arch::x86::_mm_storeu_si64
core::arch::x86::_mm_loadu_si16
core::arch::x86::_mm_loadu_si32
core::arch::wasm32::u8x16_relaxed_swizzle
core::arch::wasm32::i8x16_relaxed_swizzle
core::arch::wasm32::i32x4_relaxed_trunc_f32x4
core::arch::wasm32::u32x4_relaxed_trunc_f32x4
core::arch::wasm32::i32x4_relaxed_trunc_f64x2_zero
core::arch::wasm32::u32x4_relaxed_trunc_f64x2_zero
core::arch::wasm32::f32x4_relaxed_madd
core::arch::wasm32::f32x4_relaxed_nmadd
core::arch::wasm32::f64x2_relaxed_madd
core::arch::wasm32::f64x2_relaxed_nmadd
core::arch::wasm32::i8x16_relaxed_laneselect
core::arch::wasm32::u8x16_relaxed_laneselect
core::arch::wasm32::i16x8_relaxed_laneselect
core::arch::wasm32::u16x8_relaxed_laneselect
core::arch::wasm32::i32x4_relaxed_laneselect
core::arch::wasm32::u32x4_relaxed_laneselect
core::arch::wasm32::i64x2_relaxed_laneselect
core::arch::wasm32::u64x2_relaxed_laneselect
core::arch::wasm32::f32x4_relaxed_min
core::arch::wasm32::f32x4_relaxed_max
core::arch::wasm32::f64x2_relaxed_min
core::arch::wasm32::f64x2_relaxed_max
core::arch::wasm32::i16x8_relaxed_q15mulr
core::arch::wasm32::u16x8_relaxed_q15mulr
core::arch::wasm32::i16x8_relaxed_dot_i8x16_i7x16
core::arch::wasm32::u16x8_relaxed_dot_i8x16_i7x16
core::arch::wasm32::i32x4_relaxed_dot_i8x16_i7x16_add
core::arch::wasm32::u32x4_relaxed_dot_i8x16_i7x16_add
Следующие API теперь можно использовать в контексте const
:
std::task::Waker::from_raw
std::task::Context::from_waker
std::task::Context::waker
${integer}::from_str_radix
std::num::ParseIntError::kind
Прочие изменения
Проверьте всё, что изменилось в Rust, Cargo и Clippy.
Кто работал над 1.82.0
Многие люди собрались вместе, чтобы создать Rust 1.82.0. Без вас мы бы не справились. Спасибо!
От переводчиков
С любыми вопросами по языку Rust вам смогут помочь в русскоязычном Телеграм-чате или же в аналогичном чате для новичковых вопросов. Если у вас есть вопросы по переводам или хотите помогать с ними, то обращайтесь в чат переводчиков.
Данную статью совместными усилиями перевели andreevlex, TelegaOvoshey и funkill.