Команда Rust рада сообщить о новой версии языка — 1.66.0. Rust — это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.
Если у вас есть предыдущая версия Rust, установленная через rustup, то для обновления до версии 1.66.0 вам достаточно выполнить команду:
rustup update stable
Если у вас ещё нет rustup, то можете установить его со страницы на нашем веб-сайте, а также ознакомиться с подробным описанием выпуска 1.66.0 на GitHub.
Если вы хотите помочь нам протестировать будущие выпуски, вы можете использовать beta (rustup default beta) или nightly (rustup default nightly) канал. Пожалуйста, сообщайте обо всех встреченных вами ошибках.
Перечисления с числовым представлением теперь могут использовать явный дискриминант, даже если они имеют поля.
#[repr(u8)]
enum Foo {
A(u8),
B(i8),
C(bool) = 42,
}
Ранее вы могли использовать явные дискриминанты в перечислениях, но только если ни один из вариантов не содержал поля. Явные дискриминанты полезны при передаче значения между разными языками и если значение перечисления нужно определить в обоих языках. Например:
#[repr(u8)]
enum Bar {
A,
B,
C = 42,
D,
}
Здесь перечисление Bar гарантированно будет иметь то же представление в памяти, что и u8. Кроме того, у варианта Bar::C гарантированно есть дискриминант 42. Варианты без явно указанных значений будут иметь дискриминанты, которые автоматически назначаются в соответствии с их порядком в исходном коде, поэтому у Bar::A будет дискриминант 0, у Bar::B будет 1, а у Bar: будет 43. Без этой функции единственным способом установить явное значение Bar::C было бы добавить 41 ненужный вариант перед ним!
Примечание: в то время как для перечислений без полей можно проверить дискриминант через приведение as (например, Bar::C as u8), Rust не предоставляет способа на уровне языка для доступа к необработанному дискриминанту перечисления с полями. Вместо этого для проверки дискриминанта перечисления с полями необходимо использовать небезопасный в настоящее время код. Поскольку эта функция предназначена для использования с межъязыковым FFI, где уже необходим небезопасный код, мы надеемся, что это не станет слишком большой дополнительной нагрузкой. Пока же если всё, что вам нужно, — это непрозрачный дескриптор дискриминанта, см. функцию std::mem::discriminant.
Когда исследуется или оценивается машинный код, сгенерированный компилятором, бывает полезно отключить оптимизацию для определённых мест. В следующем примере функция push_cap 4 раза вызывает в цикле Vec:ush:
fn push_cap(v: &mut Vec<i32>) {
for i in 0..4 {
v.push(i);
}
}
pub fn bench_push() -> Duration {
let mut v = Vec::with_capacity(4);
let now = Instant::now();
push_cap(&mut v);
now.elapsed()
}
Если вы посмотрите оптимизированный вывод компилятора для x86_64, то заметите, что он выглядит достаточно коротко:
example::bench_push:
sub rsp, 24
call qword ptr [rip + std::time::Instant::now@GOTPCREL]
lea rdi, [rsp + 8]
mov qword ptr [rsp + 8], rax
mov dword ptr [rsp + 16], edx
call qword ptr [rip + std::time::Instant::elapsed@GOTPCREL]
add rsp, 24
ret
Фактически вся функция push_cap, которую мы хотели оценить, была оптимизирована!
Мы можем обойти это при помощи новой стабилизированной функции black_box. Функционально black_box не очень интересна: она лишь берёт значение, которое вы ей передали, и возвращает его обратно. Однако внутри компилятор обрабатывает black_box как функцию, которая что-то делает с передаваемым ей вводом и возвращает какое-то значение (как и следует из её названия).
Это очень полезно для отключение оптимизаций — как той, что мы видели выше. Например, мы можем подсказать компилятору, что вектор будет использоваться для чего-то после каждой итерации цикла.
use std::hint::black_box;
fn push_cap(v: &mut Vec<i32>) {
for i in 0..4 {
v.push(i);
black_box(v.as_ptr());
}
}
Теперь мы можем найти развёрнутый цикл for в нашем оптимизированном ассемблерном выводе:
mov dword ptr [rbx], 0
mov qword ptr [rsp + 8], rbx
mov dword ptr [rbx + 4], 1
mov qword ptr [rsp + 8], rbx
mov dword ptr [rbx + 8], 2
mov qword ptr [rsp + 8], rbx
mov dword ptr [rbx + 12], 3
mov qword ptr [rsp + 8], rbx
Вы также можете увидеть побочный эффект вызова black_box в этом ассемблерном выводе. Инструкция mov qword ptr [rsp + 8], rbx бесполезно повторяется после каждой итерации. Эта инструкция записывает адрес v.as_ptr() в качестве первого аргумента функции, которая фактически никогда не вызывается.
Обратите внимание, что сгенерированный код вообще не связан с возможностью распределения памяти, введённого вызовом push. Это потому, что компилятор всё ещё опирается на тот факт, что мы вызвали Vec::with_capacity(4) в функции bench_push. Вы можете поиграть с размещением black_box или попробовать использовать его в нескольких местах, чтобы увидеть его влияние на оптимизацию компилятора.
Rust 1.62.0 представил cargo add — утилиту командной строки для добавления зависимостей в ваш проект. В этой же версии вы сможете использовать cargo remove для их удаления.
Выпуск Rust 1.66 включает и другие изменения:
Проверьте всё, что изменилось в Rust, Cargo и Clippy.
Если у вас есть предыдущая версия Rust, установленная через rustup, то для обновления до версии 1.66.0 вам достаточно выполнить команду:
rustup update stable
Если у вас ещё нет rustup, то можете установить его со страницы на нашем веб-сайте, а также ознакомиться с подробным описанием выпуска 1.66.0 на GitHub.
Если вы хотите помочь нам протестировать будущие выпуски, вы можете использовать beta (rustup default beta) или nightly (rustup default nightly) канал. Пожалуйста, сообщайте обо всех встреченных вами ошибках.
Что стабилизировано в 1.66.0
Явные дискриминанты для перечислений с полями
Перечисления с числовым представлением теперь могут использовать явный дискриминант, даже если они имеют поля.
#[repr(u8)]
enum Foo {
A(u8),
B(i8),
C(bool) = 42,
}
Ранее вы могли использовать явные дискриминанты в перечислениях, но только если ни один из вариантов не содержал поля. Явные дискриминанты полезны при передаче значения между разными языками и если значение перечисления нужно определить в обоих языках. Например:
#[repr(u8)]
enum Bar {
A,
B,
C = 42,
D,
}
Здесь перечисление Bar гарантированно будет иметь то же представление в памяти, что и u8. Кроме того, у варианта Bar::C гарантированно есть дискриминант 42. Варианты без явно указанных значений будут иметь дискриминанты, которые автоматически назначаются в соответствии с их порядком в исходном коде, поэтому у Bar::A будет дискриминант 0, у Bar::B будет 1, а у Bar: будет 43. Без этой функции единственным способом установить явное значение Bar::C было бы добавить 41 ненужный вариант перед ним!
Примечание: в то время как для перечислений без полей можно проверить дискриминант через приведение as (например, Bar::C as u8), Rust не предоставляет способа на уровне языка для доступа к необработанному дискриминанту перечисления с полями. Вместо этого для проверки дискриминанта перечисления с полями необходимо использовать небезопасный в настоящее время код. Поскольку эта функция предназначена для использования с межъязыковым FFI, где уже необходим небезопасный код, мы надеемся, что это не станет слишком большой дополнительной нагрузкой. Пока же если всё, что вам нужно, — это непрозрачный дескриптор дискриминанта, см. функцию std::mem::discriminant.
core::hint::black_box
Когда исследуется или оценивается машинный код, сгенерированный компилятором, бывает полезно отключить оптимизацию для определённых мест. В следующем примере функция push_cap 4 раза вызывает в цикле Vec:ush:
fn push_cap(v: &mut Vec<i32>) {
for i in 0..4 {
v.push(i);
}
}
pub fn bench_push() -> Duration {
let mut v = Vec::with_capacity(4);
let now = Instant::now();
push_cap(&mut v);
now.elapsed()
}
Если вы посмотрите оптимизированный вывод компилятора для x86_64, то заметите, что он выглядит достаточно коротко:
example::bench_push:
sub rsp, 24
call qword ptr [rip + std::time::Instant::now@GOTPCREL]
lea rdi, [rsp + 8]
mov qword ptr [rsp + 8], rax
mov dword ptr [rsp + 16], edx
call qword ptr [rip + std::time::Instant::elapsed@GOTPCREL]
add rsp, 24
ret
Фактически вся функция push_cap, которую мы хотели оценить, была оптимизирована!
Мы можем обойти это при помощи новой стабилизированной функции black_box. Функционально black_box не очень интересна: она лишь берёт значение, которое вы ей передали, и возвращает его обратно. Однако внутри компилятор обрабатывает black_box как функцию, которая что-то делает с передаваемым ей вводом и возвращает какое-то значение (как и следует из её названия).
Это очень полезно для отключение оптимизаций — как той, что мы видели выше. Например, мы можем подсказать компилятору, что вектор будет использоваться для чего-то после каждой итерации цикла.
use std::hint::black_box;
fn push_cap(v: &mut Vec<i32>) {
for i in 0..4 {
v.push(i);
black_box(v.as_ptr());
}
}
Теперь мы можем найти развёрнутый цикл for в нашем оптимизированном ассемблерном выводе:
mov dword ptr [rbx], 0
mov qword ptr [rsp + 8], rbx
mov dword ptr [rbx + 4], 1
mov qword ptr [rsp + 8], rbx
mov dword ptr [rbx + 8], 2
mov qword ptr [rsp + 8], rbx
mov dword ptr [rbx + 12], 3
mov qword ptr [rsp + 8], rbx
Вы также можете увидеть побочный эффект вызова black_box в этом ассемблерном выводе. Инструкция mov qword ptr [rsp + 8], rbx бесполезно повторяется после каждой итерации. Эта инструкция записывает адрес v.as_ptr() в качестве первого аргумента функции, которая фактически никогда не вызывается.
Обратите внимание, что сгенерированный код вообще не связан с возможностью распределения памяти, введённого вызовом push. Это потому, что компилятор всё ещё опирается на тот факт, что мы вызвали Vec::with_capacity(4) в функции bench_push. Вы можете поиграть с размещением black_box или попробовать использовать его в нескольких местах, чтобы увидеть его влияние на оптимизацию компилятора.
cargo remove
Rust 1.62.0 представил cargo add — утилиту командной строки для добавления зависимостей в ваш проект. В этой же версии вы сможете использовать cargo remove для их удаления.
Стабилизированные API
- proc_macro::Span::source_text
- u*::{checked_add_signed, overflowing_add_signed, saturating_add_signed, wrapping_add_signed}
- i*::{checked_add_unsigned, overflowing_add_unsigned, saturating_add_unsigned, wrapping_add_unsigned}
- i*::{checked_sub_unsigned, overflowing_sub_unsigned, saturating_sub_unsigned, wrapping_sub_unsigned}
- BTreeSet::{first, last, pop_first, pop_last}
- BTreeMap::{first_key_value, last_key_value, first_entry, last_entry, pop_first, pop_last}
- Добавлена реализация AsFd для типов блокировок стандартных потоков для WASI
- impl TryFrom<Vec<T>> for Box<[T; N]>
- core::hint::black_box
- Duration::try_from_secs_{f32,f64}
- Option::unzip
- std:s::fd
Прочие изменения
Выпуск Rust 1.66 включает и другие изменения:
- Теперь вы можете использовать в шаблонах диапазоны ..=X.
- Linux-сборки теперь используют оптимизированные rustc фронтенд и LLVM бэкенд с LTO и BOLT соответственно, что оптимизирует быстродействие и использование памяти.
Проверьте всё, что изменилось в Rust, Cargo и Clippy.
Rust 1.66.0: дискриминанты для перечислений с полями, black_box, cargo remove
Команда Rust рада сообщить о новой версии языка — 1.66.0. Rust — это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение. Если у вас есть предыдущая...
habr.com