1.83K subscribers
3.24K photos
127 videos
15 files
3.52K links
Блог со звёздочкой.

Много репостов, немножко программирования.

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
#prog #rust #rustreleasenotes

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

▪️Асинхронные функции теперь могут быть рекурсивными при условии наличии индирекции. Скажем, такой код по прежнему не работает:

async fn fib(n: u32) -> u32 {
match n {
0 | 1 => 1,
_ => fib(n-1).await + fib(n-2).await
}
}


, поскольку асинхронные функции компилируются в стейт-машины, размер которых должен быть известен на этапе компиляции, а размер стека функций явно зависит от рантайм-параметра. Компилятор на это выдаёт относительно хорошую ошибку:

error[E0733]: recursion in an async fn requires boxing
--> src/lib.rs:1:1
|
1 | async fn fib(n: u32) -> u32 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: a recursive `async fn` call must introduce indirection such as `Box::pin` to avoid an infinitely sized future


(к сожалению, на сами рекурсивные вызовы не указывает)

Если воспользоваться предложением компилятора, то можно получить вот такой рабочий код:

async fn fib(n: u32) -> u32 {
match n {
0 | 1 => 1,
_ => Box::pin(fib(n-1)).await + Box::pin(fib(n-2)).await
}
}


Можно даже слегка угореть по уменьшению количества аллокаций и сделать так:

_ => Box::pin(async move { fib(n-1).await + fib(n-2).await }).await


▪️В язык добавили строковые литералы типа &'static CStr:

use core::ffi::CStr;
const _: &CStr = c"look mum, no trailing nul!";


Очень удобная вещь для интеропа с C. Эти литералы могут быть сырыми (cr"it is not --> \n <-- a newline") и поддерживают escape-последовательности для UTF-8 code point и байтовых значений за пределами ASCII. Символы за пределами ASCII кодируются в том же виде, что и в UTF-8.

▪️Добавили макрос std::mem::offset_of!. Пока что поддерживается только в форме смещения поля (полей) от начала раскладки структуры. Технически такой макрос возможно реализовать и сейчас, но для того, чтобы это сделать правильно (без UB), нужно некоторое количество не самого тривиального unsafe кода.

▪️Для слайсов добавили пачку методов, позволяющих доставать части слайса в виде ссылок на массивы:

🔸slice::first_chunk{, _mut}
🔸slice::split_first_chunk{, _mut}
🔸slice::last_chunk{, _mut}
🔸slice::split_last_chunk{, _mut}

Также добавили методы slice::chunk_by{, _mut}, который возвращает итератор из частей слайсов, в которых последовательные пары элементов удовлетворяют предоставленному предикату:

let slice = &[1, 1, 1, 3, 3, 2, 2, 2];

let mut iter = slice.chunk_by(|a, b| a == b);

assert_eq!(iter.next(), Some(&[1, 1, 1][..]));
assert_eq!(iter.next(), Some(&[3, 3][..]));
assert_eq!(iter.next(), Some(&[2, 2, 2][..]));
assert_eq!(iter.next(), None);

▪️Опубликован крейт cargo-util-schemas, который является публичной схемой Cargo.toml , используемой в самом cargo.

▪️Rustdoc теперь поддерживает ссылки в заголовках и позволяет искать по типам, используя естественный синтаксис для кортежей (со скобочками).
👍10
#prog #rust #rustreleasenotes

Вышла версия Rust 1.78.0! Как всегда, тут только то, что показалось мне интересным, а всё остальное в детальных заметках о релизе.

▪️Появилось новое пространство имён для атрибутов: diagnostic. В настоящий момент там только один атрибут: on_unimplemented (о котором я рассказывал). Он позволяет кастомизировать сообщение, выдаваемое компилятором для случаев, когда обобщённому коду, который требует этот трейт на обобщённом параметре, предоставляется тип, не реализующий этот трейт. Это уже используется в axum.

▪️В std при debug_assertions теперь проверяются некоторые из предусловий на unsafe функциях. Раньше это было невозможно из-за того, что std всегда поставлялась в релизной сборке.

▪️Некоторые из функций на указателях стали более полезными, поскольку теперь обещают более строгие результаты.
🔸pointer::align_offset возвращает смещение, необходимое для того, чтобы выровнять указатель до указанного выравнивания, или usize::MAX, если это невозможно. Раньше ей разрешалось всегда возвращать usize::MAX.
🔸slice::align_to и slice::align_to_mut переводят &{mut} [T] в (&{mut} [T], &{mut} [U], &{mut} [T]), где слайс в середине теперь максимально возможного размера с учётом ограничений на выравнивание и размер. Раньше функциям разрешалось возвращать, скажем, исходный слайс целиком как первый элемент тройки.

Обе оговорки были связаны с применением этих функций в const-контексте. Сейчас их убрали, поскольку они и сейчас нестабильны в const-контекстах.

▪️async-методы теперь могут возвращать в реализациях конкретные типы, реализующие Future (а не только impl Future).

▪️Реализация RwLock теперь полностью кастомная и не зависит от pthread. Это позволяет оградиться от багов на старых системах, а также избежать аллокаций (т. к. примитивы синхронизации pthread неперемещаемы) и повысить производительность.

▪️В метод char::is_grapheme_extended добавлена проверка на ASCII, чтобы избежать лукапа по юникодным таблицам (последовавший за ним PR переместил эту проверку в код, генерируемый по таблицам Unicode). Звучит, как что-то узкоспециализированное, но этот метод в конечном счёте вызывается в реализации Debug для str. Как следствие, это изменение более чем вдвое ускорило дерайв Debug!

▪️В паттернах теперь нельзя использовать константы типов, не реализующих PartialEq и NaN.

▪️Компилятор теперь по умолчанию не компилирует код с неверными #[doc]-атрибутами.

▪️Компилятор теперь детектирует больше избыточных импортов. Это изменение позволило убрать лишние импорты и в самом компиляторе во многих местах.

▪️Компилятор теперь предлагает переместить macro_rules! выше по тексту, если декларативный макрос вызывается раньше, чем определяется в этом файле.

▪️Компилятор теперь диагностирует каст ссылки из одного типа в другой с бо́льшим размером.
👍8🔥3
#prog #rust #rustreleasenotes

Вышла версия Rust 1.79.0! Как обычно, тут только избранные моменты, остальное в полных заметках.

▪️Стабилизированы inline const блоки! Одно из самых непосредственно полезных применений — array repeat expression. Если раньше приходилось писать что-то в духе:

const EMPTY: Option<Vec<u8>> = None;
let foo = [EMPTY; 100];

, то теперь можно писать просто

let foo = [const { None }; 100];

(конечно, при условии, что вывод типов поймёт конкретный тип Option внутри массива). Более того, в обобщённых контекстах это тоже работает:

fn create_none_array<T, const N: usize>() -> [Option<T>; N] {
[const { None }; N]
}

Ещё одно полезное применение — статические ассерты. Вместо того, чтобы выписывать _-константы, можно просто окружить ассерт const {}:

fn broken() {
const { assert!(2 + 2 == 5) } // ошибка компиляции
}

Это тоже работает в обобщённых контекстах, но по очевидным причинам при зависимости от обобщённых параметров может выстрелить только при мономорфизации:

use std::mem::size_of;

const fn foo<T>() -> usize {
const { assert!(size_of::<T>() > 1) }
size_of::<T>() - 1
}

fn main() {
_ = foo::<i32>();
_ = foo::<u8>(); // ошибка компиляции
}

К сожалению, сейчас у этой фичи есть ограничения. Именно, const-блоки можно использовать только в выражениях, но нельзя, в отличие от обычных констант, использовать их в паттернах (на это сейчас есть отдельная фича). Так что следующий код, к сожалению, пока что требует nightly (и #![feature(inline_const_pat)]):

enum Enum {
Foo,
Bar,
Baz,
}

impl Enum {
fn from_usize(n: usize) -> Result<Self, usize> {
use Enum::{Foo, Bar, Baz};

Ok(match n {
const { Foo as _ } => Foo,
const { Bar as _ } => Bar,
const { Baz as _ } => Baz,
_ => return Err(n),
})
}
}

Ещё одна особенность — если const-блок находится в статически недостижимом коде, то он может быть не вычислен и, соответственно, не вызвать ошибку компиляции на ошибочной константе.

▪️Теперь можно писать ограничения на ассоциированный тип прямо в баундах: T: Trait<Assoc: Bounds...>. В stabilization report рассказывается более подробно, чем это полезно. Одно из применений — возможность указывать ограничения на ассоциированных типах на неназываемых impl Trait типах.

▪️Продление времени жизни ссылок на временные значения теперь работает и при возврате из if и match. Этот код компилируется сейчас и не компилировался раньше:

let a = String::from("a");
let _ = if true { &a } else { &String::from("b") };
let _ = match () {
() => &String::from("c"),
};

▪️main теперь может быть реэкспортом, а не определена непосредственно в корне иерархии:

mod foo {
pub(crate) fn bar() {}
}
use foo::bar as main;


Так можно использовать и функцию из другого крейта.

▪️Команды cargo теперь по умолчанию учитывают MSRV (minimal supported Rust version) при выборе версий зависимостей.

▪️rustdoc теперь показывает имена в результатах поиска один раз, даже если они доступны по нескольким путям из-за реэкспортов.

▪️У трейта Clone есть метод clone_from с сигнатурой fn(&mut self, source: &Self), который позволяет перезаписать содержимое self напрямую вместо того, чтобы замещать self полностью свежей копией. Однако это только возможность, и большинство типов используют реализацию по умолчанию: *self = source.clone(). Более того, для некоторых типов переопределение этого метода может иметь наблюдаемое изменение поведения по сравнению с реализацией по умолчанию, даже если clone_from не имеет намеренно отличную семантику. По этой причине std теперь документирует переопределённый метод clone_from на коллекциях.
🔥3
#prog #rust #rustreleasenotes

Вышла версия Rust 1.80.0! Как всегда, тут только избранные моменты, а остальное в детальных заметках о релизе.

(rust playground всё ещё не обновился, так что примеры кода проверяйте локально или на nightly)

▪️В паттернах теперь можно использовать диапазоны с исключающей верхней границей. Пример из блога:

fn size_prefix(n: u32) -> &'static str {
const K: u32 = 10u32.pow(3);
const M: u32 = 10u32.pow(6);
const G: u32 = 10u32.pow(9);
match n {
..K => "",
K..M => "k",
M..G => "M",
G.. => "G",
}
}


▪️Важное изменение для unsafe кода: теперь на произвольных выровненных указателях разрешены чтение и запись значений типов нулевого размера, включая null. Также на всех указателях (и null) разрешены смещения на 0, и offset_from всегда разрешён для указателей с одинаковым адресом из одной аллокации.

▪️В релизе Rust 1.75.0 были даны гарантии на раскладку в памяти Option для некоторых типов т. н. "null pointer optimization" (название не вполне верное, поскольку не только у ссылок есть ниша на битовом паттерне из одних нулевых битов). В этом релизе схожие гарантии дали и для раскладки Result. Именно, Result имеет такое же представление в памяти, как и аналогичный Option с одним из ти‌повых параметров, если для этого параметра есть гарантии "null pointer optimization" при оборачивании в Option, а второй ти‌повый параметр является типом нулевого размера с выравниванием 1, без полей и без атрибута #[non_exhaustive].

Например, Result<NonZero<i32>, ()> и Result<(), NonZero<i32>> теперь имеют такие же размер, выравнивание и гарантии ABI, как и Option<NonZero<i32>>.

⚠️ std теперь может уронить программу в бо‌льшем числе случаев. Смотри ниже:

🔸Помните, в Rust 1.63 добавили типы для IO safety? Так вот, в отладочной сборке (читай, при #[cfg(debug_assertions)]) дроп OwnedFd теперь прерывает работу процесса, если соответствующий файловый дескриптор уже закрыт.
🔸Также в отладочной сборке unchecked_* арифметические методы паникуют при нарушении предусловий их безопасного использования.
🔸Тип fmt::Error должен использоваться лишь для индикации того, что запись в нижележащий приёмник информации более невозможна, но не для передачи ошибок в самом процессе форматирования как таковом. В частности, в силу того, что String может расширяться по мере нужды, форматирование в String считается операцией, которая не может вернуть ошибку. Для того, чтобы усилить соблюдение этого контракта, теперь форматирование в новый String паникует при ошибке из реализации одного из fmt методов, а форматирование в IO-ресурсы (стандартный вывод, например) паникует, если ошибку вернул метод форматирования, но не запись данных в IO-ресурс.
🔸PathBuf::set_extension теперь паникует, если новое расширение содержит разделитель пути файловой системы.

▪️Добавлены типы LazyCell и LazyLock, которые имитируют автора этого канала позволяют хранить значение, вычисляемое по требованию при помощи предоставленной при создании функции. Значит ли это, что теперь можно выкинуть once_cell? Отнюдь. У этих типов не стабилизированы методы для получения содержимого без форсирования их вычисления, и по непонятным причинам оба не дают никакого способа (даже нестабильного) получить мутабельный доступ к содержимому.

Более того, получения значения из LazyLock блокирует вызывающий поток, если значение ещё не вычислено или в процессе вычисления. Как правило, это то, что нужно для корректной логики, но в некоторых случаях можно или даже нужно позволить сразу нескольким потокам вычислять значение одновременно и установить в итоге то, которое было у самого первого потока, завершившего работу (например, время старта первого рабочего потока в тредпуле). Для таких юзкейсов в once_cell есть типы из модуля race. Помимо логического отличия, эти типы за счёт отказа от блокировки потоков могут быть использованы на платформах без операционной системы.
👍8😁1
#prog #rust #rustreleasenotes

Вышла версия Rust 1.81.0! Как всегда, котлеты детальные заметки о релизе отдельно, а мухи мои хайлайты отдельно.

▪️Компилятор теперь несколько иначе выводит лайфтаймы. Раньше при наличии self компилятор выводил время жизни из ссылки на Self, если оно было. Теперь компилятор ищет ссылку не непосредственно на Self, а на тип, включающий в себя Self, и прерывает компиляцию, если в типе self больше одного лайфтайма. Примеры из PR:

// ранее компилировалось, а теперь нет
fn a(self: &Box<&Self>) -> &u32
fn b(self: &Pin<&mut Self>) -> &String
fn c(self: &mut &Self) -> Option<&Self>
fn d(self: &mut &Box<Self>, arg: &usize) -> &usize

// ранее не компилировалось, теперь компилируется
fn f(self: &Box<Self>) -> &u32

// компилируется, но теперь выбирает иной лайфтайм
fn e(self: &mut Box<Self>, arg: &usize) -> &usize
// ^ теперь ^ раньше


▪️Ещё насчёт лайфтаймов: компилятор теперь по умолчанию не компилирует код с ассоциированными константами с лайфтаймами в типах, если есть именованные лайфтаймы в текущей области видимости:

struct Foo<T>(T);

impl<'a> Foo<&'a ()> {
const STR: &str = "hello, world"; // низзя (но можно #[allow], если хочется)
}

impl Foo<()> {
const STR: &str = "hello, world"; // можно, других лт нет, выводится 'static
}


▪️Стабилизировали атрибут #[expect], который заставляет компилятор выдавать предупреждение, если указанный линт не срабатывает. Пример:

fn consume(_: i32) {
todo!()
}

pub fn foo() {
let mut x = 0; // warning: variable does not need to be mutable
consume(x);
}

pub fn bar() {
#[expect(unused_mut)]
let mut x = 0; // нет предупреждений
consume(x);
}


Эта вещь особенно полезна при написании чернового варианта кода. В отличие от #[allow], компилятор будет выдавать предупреждение, когда весь код, вызывающий срабатывание указанного линта, будет исправлен.

▪️Ещё изменение касательно атрибутов для линтов: если подобный линт вызывает предупреждение компилятора, то оно теперь непосредственно включает сообщение из reason:

fn consume(_: i32) {}

#[deny(unused_mut, reason = "mutability is evil")]
pub fn f() {
let mut x = 0;
consume(x);
}


Вывод:

error: variable does not need to be mutable
--> src/lib.rs:5:9
|
5 | let mut x = 0;
| ----^
| |
| help: remove this `mut`
|
= note: mutability is evil
note: the lint level is defined here
--> src/lib.rs:3:8
|
3 | #[deny(unused_mut, reason = "mutability is evil")]


Строка = note: mutability is evil ранее отсутствовала в выводе.

▪️У большинства вариантов ABI в Rust есть *-unwind варианты, которые позволяют паникам пересекать границу FFI. Для вариантов ABI без *-unwind такое поведение является UB. В этой версии Rust программа теперь прекращает работу (abort), когда паника пересекает границу ABI, не поддерживающего раскрутку стека.

▪️offset_from теперь можно всегда применять на указателях с одинаковыми адресами, вне зависимости от их происхождения (provenance). Да, в том числе на указателях из разных аллокаций. Сделано это для достижения provenance monotonicity: добавления произвольного provenance указателям не может добавить UB в программу, в которой UB не было.

▪️{Rc,Arc}::make_mut() теперь работают на слайсах из клонируемых значений. Реализовано через отдельный нестабильный трейт, так что для своих типов реализовать не получится, увы.

▪️До стейбла дошли улучшения сортировок. Да, теперь ваш говнокод в реализации Ord может вызвать панику.
👍3
#prog #rust #rustreleasenotes

Вышла версия Rust 1.82.0! Как всегда, тут только избранные части, а полный ченджлог отдельно.

▪️Теперь в некоторых случаях можно использовать pattern matching, не упоминая ненаселённые паттерны:

enum Void {}

fn foo() -> Result<u32, Void> {
todo!()
}

fn main() {
let Ok(x) = foo();

match foo() {
Ok(x) => {}
}
}


▪️Добавили новый синтаксис, который позволяет явно указать, какие лайфтайм-параметры будут использованы для обобщённых параметров типа impl Trait в возвращаемой позиции: + use<'a>. Это позволяет, в частности, убрать из списка лайфтайм-параметров те, которые по факту не используются в возвращаемом типе. У меня не получилось кратко объяснить, для чего это нужно, читайте блогпост. Из досадных ограничений: анонимный тип обязан перечислять в use все обобщённые типы и константные параметры из окружения, даже если они не используются. Это планируют исправить в будущих версиях.

▪️Стабилизировали операции на числами с плавающей точкой в const fn. В качестве не вполне приятного дополнения const fn теперь могут возвращать разные результаты в о время компиляции и в рантайме. С другой стороны, в настоящий момент эта оговорка существует лишь из-за тонкостей насчёт гарантий битового представления NaN. Если вас это не интересует (а так оно, скорее всего, и есть), то const fn продолжают сохранять равенство рантайм и компилтайм поведения.

▪️Стабилизировали операции для взятия адреса place expression без создания промежуточной ссылки: &raw const и &raw mut, ранее доступные через макросы std::ptr::addr_of{, _mut}. Также в качестве багфикса их использование на static определениях теперь не триггерит сообщение о небезопасной операции.

▪️В Rust добавили ключевое слово safe... Ну, почти. extern-определения по определению не могут быть проверены компилятором на корректность, поэтому в общем случае доступ к ним требует unsafe-блока. Начиная с этой версии определение в extern-блоке можно пометить, как safe, обозначая тем самым, что доступ к определению не требует соблюдения каких-либо специальных предусловий. Например, таковыми можно пометить большинство функций из libm — математической составляющей стандартной библиотеки C.

▪️Макрос offset_of! теперь можно использовать для вычисления сдвига вложенного поля:

struct Inner {
field: u32,
}

struct Outer {
inner: Inner,
}

const _: usize = std::mem::offset_of!(Outer, inner.field);


▪️Некоторые атрибуты — например, #[no_mangle] и #[export_name] — могут приводить к некорректным программаv даже в отсутствие какого-либо unsafe кода. Например, вот эта простая программа при запуске на *nix-системе почти наверняка упадёт с SIGSEGV:

fn main() {
println!("Hello, world!");
}

#[no_mangle]
fn malloc() -> usize { 1 }


С edition 2024 (да, ещё не стабилизированной) подобные атрибуты нужно будет помечать, как unsafe:

#[unsafe(no_mangle)]
fn this_is_not_in_global_namespace() {}


В предыдущих edition по соображениям обратной совместимости это не требуется, но есть соответствующий линт (по умолчанию выключенный).

⬇️⬇️⬇️
👍7🔥6🥰1😍1
#prog #rust #rustreleasenotes

Вышла версия Rust 1.83.0! Как всегда, тут только избранные части, а всё остальное в детальных заметках.

▪️В const-контекстах теперь можно использовать &mut-ссылки, а также &-ссылки на типы с внутренней изменяемостью!

const fn inc(x: &mut i32) {
*x += 1;
}

const C: i32 = {
let mut c = 41;
inc(&mut c);
c
};


Написание const-кода серьёзно упростилось. Само итоговое значение константы, впрочем, не может быть изменяемой (в том числе через внутреннюю изменяемость) ссылкой.

Также итоговое значение константы теперь может содержать &-ссылку на static, если в типе нет внутренней изменяемости.

static S: i32 = 25;
const C: &i32 = &S;


По понятным причинам итоговое значение константы не может содержать изменяемую (в том числе через внутреннюю изменяемость) ссылку на static.

▪️Как прямое следствие предыдущего пункта — кучу функций можно теперь использовать в const-контексте, включая множество операций над сырыми указателями (в том числе NonNull) и различные функции для разбиения мутабельных слайсов на части.

▪️Также стабилизировали кучу новых вариантов io::ErrorKind. Забавный факт: эти варианты были и раньше и использовались в std, и в теории по ним можно было матчиться, используя реализацию Display. Ещё забавный факт: стабилизацию всех этих вариантов и ещё некоторых запланировали 23 месяца назад, но первый PR со стабилизацией застопорился из-за некоторых вариантов, которые были слишком специфичны для конкретных операционных систем. Собственно, новый PR со стабилизацией приняли лишь потому, что эти варианты из списка на стабилизацию выкинули.

▪️Ещё в число стабилизированных API вошли hash_map::{Entry, VacantEntry}::insert_entry. Эти методы вставляют переданное значение, но, в отличие от insert, возвращают не мутабельную ссылку на значение, а OccupiedEntry.

▪️Одновременные атомарные и неатомарные доступы на чтение из одной локации в памяти одного размера теперь не считаются гонкой данных и потому не являются UB (в C++ — является, поскольку атомарные доступы в C++ происходят через создание atomic_ref, абстрактная машина C++ оперирует типизированной памятью).

▪️Предыдущие версии языка ошибочно считали #[non_exhaustive]-структуры с ненаселёнными полями населёнными типами вне крейта, в которых они определены. В этой версии это странное поведение исправили. Для пользователей языка это означает, что, например, с вот таким определением:

enum Empty {}

#[non_exhaustive]
struct Foo(Empty);


следующая функция скомпилируется вне зависимости от того, определена ли она в том же крейте, что и Foo, или нет:

fn eliminate<T>(f: Foo) -> T {
match f {}
}


▪️Из забавного: компилятор теперь выдаёт ошибку на атрибут #[repr(Rust)], применённый на определение, которое не является перечислением, структурой или объединением.
👍84🎉2
#prog #rust #rustreleasenotes

Вышла версия Rust 1.84.0! Как всегда, тут только некоторые вещи, а остальное в полных заметках о релизе.

▪️Когерентность — свойство системы типов Rust: для любой наперёд заданной пары трейта и типа существует не более одного impl-а этого трейта для этого типа. Очень важное свойство, которое позволяет генерировать код и не беспокоится, что один и тот же метод будет означать разные вещи в разных местах кодовой базы. Новый порешатель трейтов trait solver (нет, не chalk, хотя и вдохновлён им) теперь используется для проверки когерентности. С точки зрения программиста это означает, что компилятор теперь корректно находит больше перекрывающихся impl-ов и корректно различает больше impl-ов, которые точно не перекрываются. Если вы пытались соорудить какую-то type-level фигню, которая не работала из-за conflicting implementations..., хотя вроде как должна — попробуйте сейчас. И да, это означает, что ваш код, вероятно, компилировался по недосмотру из-за бага компилятора, как derive-visitor (не волнуйтесь, там код уже поправили).

▪️#[deny(lint_name)] внутри #[forbid(lint_name)] теперь работает и продолжает запрещать lint_name, и нет, это не позволяет переопределить forbid. Само по себе не очень полезно, но это изменение хорошо для макросов, которые генерирую код с #[deny] в контексте, где действует #[forbid]. Предупреждения на это не выдаётся (к моему неудовольствию).

▪️У трейт-объектов, составленных из Trait + AutoTrait, при приведении теперь можно отбрасывать части, которые не соответствуют авто-трейтам. Например, теперь компилируется этот код:

trait Trait {}

fn upcast(x: &(dyn Trait + Send)) -> &dyn Send { x }


▪️Я уже ссылался на статью про различие place expression. Так как непосредственно разыменовывание указателя не является UB — UB может являться последующая конвертация из place expression в value expression — взятие &raw {, const} (или, аналогично, addr_of{, _mut}!) от результата разыменовывания сырого указателя всегда является безопасной операцией. Да, даже если указатель null. Взятие адреса от доступа к полю place expression остаётся в общем случае unsafe, поскольку это требует, чтобы указатель был в пределах выделенной под значения указываемого типа памяти.

▪️Косвенно связанное изменение — добавили линт на образование сырого указателя, который немедленно становится висячим из-за того, что указывает на дропнутое временное значение. Это расширило предыдущий аналогичный линт, который предупреждал о сыром указателе из временной CString. В коде отмечается, что у линта пока есть false negative, так что целиком полагаться на это пока не стоит. С другой стороны, там же указаны конкретные операции, которые пока не проверяются, так что стоит ожидать увеличения полноты этого линта в будущем.

▪️Как я уже ссылался, касты между указателями и числами имеют на удивлению неоднозначную семантику — главным образом потому, что для корректной оптимизации кода с указателями надо знать их происхождение (provenance), а эта информация при касте из указателя в число теряется, и её потом неоткуда взять при обратном касте из числа в указатель. В стандартную библиотеку добавили API для манипуляции с адресами указателей, которые более явны насчёт того, что при этом происходит с provenance. Подробнее в документации на указатели.

▪️На примитивных численных типах стабилизировали метод isqrt, возвращающий округлённый вниз квадратный корень. Для знаковых чисел этот метод паникует на негативных значениях. Для обработки этой ситуации можно использовать checked_isqrt, который возвращает None для негативных значений. У NonZero этот метод тоже есть, но почему-то только для беззнаковых оборачиваемых типов.

▪️cargo теперь может выбирать версии зависимостей, принимая во внимание их MSRV (minimum supported Rust version). Это opt-in, но будет использоваться по умолчанию в третьей версии резолвера зависимостей, которая будет использоваться по умолчанию в edition 2024 (стабилизация в следующей версии!)
👍93🤯1
#prog #rust #rustreleasenotes

(да, я пишу с опозданием почти в неделю, и что?)

Вышла версия Rust 1.85.0! Как всегда, тут только интересные мне части, остальное в полных заметках о релизе.

▪️Релизнули новую редакцию Rust 2024 edition! К сожалению, новая версия диапазонов не попала в эту версию.
🔸в if let значение, по которому идёт сопоставление с образцом, теперь дропается перед входом в else ветку вместо после конца if let. Это позволило, в частности, избежать багов, когда guard из примитива синхронизации живёт дольше необходимого.
🔸также временные значения из последнего выражения в блоке теперь дропаются в конце блока, а не продолжают жить после него. Без этого изменения некоторый вполне нормально выглядящий код не компилировался:

// не компилируется до Rust 2024, компилируется после
fn f() -> usize {
let c = RefCell::new("..");
c.borrow().len()
}

🔸запрещены некоторые паттерны, которые крайне неочевидным образом взаимодействуют с pattern match ergonomics. Посмотрите по ссылке, там действительно странные вещи.
🔸std::env::set_var, std::env::remove_var и std::os::unix::process::CommandExt::before_exec теперь являются unsafe функциями. Сделать их unsafe во всех редакциях, увы, мешает обратная совместимость.
🔸cargo теперь по умолчанию учитывает MSRV при выборе версии зависимости.
Пачка изменений, связанных с unsafe кодом:
🔸extern-блоки теперь должны быть объявлены с unsafe, чтобы показать, что ответственность за корректность определений лежит на том, кто пишет extern блок. Дополнительно определения внутри extern-блока теперь могут быть помечены safe. В этом случае компилятор будет предполагать, что для доступа и использования этих определений не нужны специальные предусловия, и их можно использовать вне unsafe-блоков. В качестве примера можно привести sqrt из стандартной библиотеки C: не смотря на то, что это внешняя функция, её можно вызвать с любым значением, не вызывая UB.
🔸атрибуты no_mangle, export_name и link_section теперь должны быть записаны через #[unsafe(...)]. С этими атрибутами можно сломать гарантии, которые даёт компилятор, и компилятор не может проверить, что их использование корректно.
🔸unsafe_op_in_unsafe_fn warn по умолчанию.
🔸ссылки на static mut являются по умолчанию ошибкой компиляции.
🔸если компилятор не может вывести тип, к которому нужно привести выражение типа !, то он использует т. н. fallback. До Rust 2024 это был (), теперь это !. Так как это может сломать существующий unsafe-код, компилятор теперь по умолчанию даёт ошибку компиляции, если такое приведение происходит в unsafe-коде.

▪️Теперь замыкания могут быть async!

let mut vec: Vec<String> = vec![];

let closure = async || {
vec.push(ready(String::from("")).await);
};


В отличие от замыканий, возвращающих async-блок, футуры, возвращаемые этими замыканиями, могут использовать ссылки на то, что замыкание захватывает. В типах это выражается тем, что возвращаемые async-замыканиями футуры связаны временем жизни с самим замыканием. Из-за того, что эту связь пока что невозможно выразить с обычными Fn*-трейтами, в core::ops добавили AsyncFnOnce, AsyncFnMut и AsyncFn. Как и обычные Fn*-трейты, в ограничениях их можно использовать лишь в засахаренном виде.

▪️FromIterator и Extend теперь реализованы для всех кортежей до арности 12 включительно и имеют семантику, аналогичную Iterator::unzip.

▪️Для беззнаковых чисел, соответствующих NonZero и чисел с плавающей точкой добавили метод midpoint. Тривиальная вещь, но из-за переполнения её очень легко реализовать неправильно.

▪️В const-контексте теперь можно использовать, помимо прочего, несколько простых арифметических функций на числах с плавающей точкой и swap, как на ссылках, так и на сырых указателях.

▪️macro_rules, объявленные макросами, теперь используют редакцию кода, в котором они раскрываются, вместо редакции кода, в котором определён внешний макрос.
🎉12❤‍🔥2👍1
#prog #rust #rustreleasenotes

Вышла версия Rust 1.86.0! Как всегда, тут только примечательное для меня, а остальное — в полных заметках о релизе.

В этот раз релиз, по большому счёту, состоит из одной фичи (двух, если вы пишете код с SIMD).

▪️Как я уже писал, стабилизировали апкаст трейтов:

trait Super1 {}
trait Super2 {}

trait Combined: Super1 + Super2 {}

fn foo(x: &dyn Combined) {
let _: &dyn Super1 = x;
let _: &dyn Super2 = x;
}

Этот апкаст также работает и через сырые указатели, поэтому пока что — до тех пор, пока не определились с точными гарантиями — создавать сырые указатели на dyn Trait с невалидной vtable-частью запрещено и считается UB. В miri на этот счёт уже есть соответствующие проверки.

▪️По умолчанию компилятор Rust генерирует исполняемые файлы, которые могут работать на всех процессорах целевой платформы. Это означает, помимо всего прочего, невозможность использования процессорно-специфичных наборов инструкций (на практике важнее всего расширения для SIMD). Для того, чтобы обойти эти ограничения и заставить компилятор генерировать код с использованием дополнительного конкретного набора инструкций, в языке есть атрибут #[target_feature(enable = "...")]. Он позволяет компилятору генерировать код с использованием расширенного набора инструкций.

Так как использование на процессоре инструкций, которые он не распознаёт — неопределённое поведение, вызов подобных функций должен быть только в unsafe блоке. Программист при вызове обещает, что этот код недостижим на платформах, на которых указанный набор инструкций не поддерживается.

Это логичное поведение, но у начальной реализации был заметный эргономический недостаток: вызов функции с, скажем, #[target_feature(enable = "avx")] из другой функции с #[target_feature(enable = "avx")] также требовал unsafe-блока, даже не смотря на то, что требование на корректный вызов в этом случается удовлетворяется автоматически и это можно проверить статически. Начиная с этой версии подобные функции можно вызывать друг из друга без unsafe-блока при условии, что наборы включённых фич совпадают.

▪️Продолжая тему, начатую в Rust 1.70.0, компилятор теперь при компиляции в отладочном профиле (и с флагом -C debug-assertions) вставляет паникующие проверки на null при разыменовывании сырых указателей. К сожалению, так как std поставляется только в release-сборке, на коде из std это не сказывается. (Это можно обойти, но требует nightly).

Эта проверка уже нашла ошибки в реальных проектах, как во время crater run, так и после мерджа.

▪️Ошибка FromBytesWithNulError, которая может быть возвращена из CStr::from_bytes_with_nul, теперь является enum и позволяет в случае nul внутри данных получить его позицию.

▪️Теперь можно получить мутабельные ссылки по нескольким индексам/ключам из слайса и хэшмапы одновременно с новыми методами <[_]>::get_disjoint_mut и HashMap::get_disjoint_mut, вместе с unsafe *_unchecked вариантами, дающими UB при пересечении. Версия для слайсов при этом также позволяет индексировать диапазонами, а не только единичными индексами.

Что мне не очень нравится в этих методах — неконсистентность. Именно, версия для слайсов возвращает массив ссылок или ошибку, причём как в случае пересекающихся индексов, так и в случае индексов, выходящих за длину слайса. Версия же для мапы возвращает массив опциональных ссылок (с None для ключей без значений) и паникует на пересекающихся ключах.

▪️Once и OnceLock обзавелись методами для блокировки в ожидании инициализации.

▪️В const-контексте теперь можно, помимо всего прочего, разбивать строку по индексу (включая мутабельно и с опциональным возвратом) и проверять, что индекс лежит между отдельными char-ами.

▪️Компилятор для Linux на ARM теперь компилируется с ThinLTO и PGO, что ускоряет его на бенчмарках на 10-20%, в некоторых случаях — даже на 30%!.
👍93🔥1
#prog #rust #rustreleasenotes

Вышла версия Rust 1.87.0! Как всегда, тут только выдержки, всё остальное — в полных заметках о релизе.

▪️Не фича, но: прошло ровно 10 лет с момента релиза Rust 1.0 🎉

▪️В качестве продолжения темы из предыдущего релиза очень много интринсиков из std::arch теперь можно вызывать без unsafe-блоков в функциях, на которых есть #[target_feature], включающая набор инструкций с этими интринсиками.

▪️Точное указывание захватываемых обобщённых параметров (use<...>) теперь работает и для impl Trait в методах трейтов. К сожалению, опускать обобщённые типовые и const-овые параметры всё ещё нельзя.

▪️При написании impl-а трейта на dyn Trait теперь необязательно реализовывать методы с Self: Sized, так как их всё равно нельзя вызвать.

▪️Однажды в компиляторе был баг, из-за которого dyn A + B и dyn B + A считались разными типами и, как следствие, для них можно было написать две разные реализации одного трейта. Баг поправили, но, так как это номинально слом обратной совместимости, оставили это предупреждением. С версии 1.87.0 это теперь ошибка компиляции.

▪️Унарные операторы теперь корректно парсятся при применении к диапазонам с открытой нижней границей. Как следствие, эта синтаксическая конструкция теперь корректно матчится с макро-фрагментом expr в декларативных макросах, что может привести к тому, что некоторые макросы теперь могут раскрываться иначе.

▪️В стандартную библиотеку добавили поддержку каналов (в смысле pipes). Из неприятного: так как это тонкая обёртка над примитивами OS, указать ёмкость этих каналов нельзя.

▪️Для примитивных чисел добавили методы unbounded_shl и unbounded_shr. Обычные битовые сдвиги сдвигают числа не на указанное число бит, а на число бит, равное остатку от деления указанного числа на битовую ширину. Это хорошо ложится на команды процессора, но это — крайне неполезная для программиста семантика. unbounded_* методы лишены этой проблемы: при сдвиге на значение, большее битовой ширины, методы возвращают 0 для сдвига влево, 0 для сдвига вправо у беззнаковых чисел и размноженный знаковый бит для знаковых чисел (то есть 0 для положительных и -1 для отрицательных).

▪️У знаковых чисел добавили метод cast_unsigned, а у беззнаковых — cast_signed (включая NonZero). Да, это полностью аналогично использованию кастов через as, но эти методы позволяют убедиться, что битовая ширина не меняется.

▪️Ещё беззнаковым числам добавили метод is_multiple_of. Казалось бы, зачем?

This function is equivalent to self % rhs == 0, except that it will not panic for rhs == 0. Instead, 0.is_multiple_of(0) == true, and for any non-zero n, n.is_multiple_of(0) == false.


▪️Из несколько более значимого: стабилизировали extract_if для вектора и для связного списка. Оба итератора извлекают из коллекции элементы, соответствующие предикату, и оба позволяют модифицировать переданные элементы. Версия для вектора ещё и позволяет указать диапазон, на котором применяется фильтрация.

▪️Также коллекцию методов take_* на слайсах для извлечения указанных частей (со странными на первый взгляд типами self: &mut &{,mut}[T]) стабилизировали с именами split_off_*

▪️OsStr{, ing} теперь имеют методы display, которые возвращают реализующую Display обёртку. По очевидным причинам обёртка может при выводе терять данные из-за невозможности представить некоторые элементы в UTF-8.

▪️Целую пачку API стабилизировали для использования в const-контекстах. Наиболее значимым, пожалуй, является <[T]>::copy_from_slice.

▪️Вывод Debug для сырых указателей теперь включает не только адрес, но и метаданные, при наличии.

▪️Парочка изменений, связанных с fmt-макросами. Во-первых, в одной из версий из-за изменений внутреннего представления стало возможным использовать format_args! (в том числе не напрямую, а через panic!) вида format_args!("{}", "a") в const-контекстах. Эта было ненамеренно, поэтому в 1.87.0 эту возможность прикрыли. Во-вторых, значения для указания максимальной ширины форматирования и максимальной точности теперь ограничены 16 битами. Звучит смешно, но некоторые либы это сломало.
👍11🥰3🥴1
#prog #rust #rustreleasenotes

Вышла версия Rust 1.88.0! Как всегда, тут только кусочки, всё остальное — в детальных заметках о релизе.

▪️Наконец-то стабилизировали let chains! 🎉🎉🎉 Джва года ждал. Пример утащу прям из блогопоста:

if let Channel::Stable(v) = release_info()
&& let Semver { major, minor, .. } = v
&& major == 1
&& minor == 88
{
println!("`let_chains` was stabilized in this version");
}


К сожалению, из-за соображений обратной совместимости, связанных с временем жизни временных выражений в if let, эта фича доступна только в edition 2024.

▪️Специфическая вещь: стабилизировали так называемые naked functions — функции, для которых не генерирует при кодгене пролог и эпилог. Их тело состоит из ассемблерного кода. Периодически требуется для написания низкоуровневого кода.

▪️В cfg-атрибутах теперь можно использовать литералы true и false. Как пишут в блогопосте:

Previously, empty predicate lists could be used for unconditional configuration, like cfg(all()) for enabled and cfg(any()) for disabled, but this meaning is rather implicit and easy to get backwards. cfg(true) and cfg(false) offer a more direct way to say what you mean.


▪️В Rust вызов методов автоматически создаёт ссылку, если метод вызывается на значении. Это удобно, но иногда позволяет написать код, который делает потенциально небезопасную операцию и при этом не выглядит таковым. Именно, если значение, на котором вызывается метод, является результатом разыменовывания сырого указателя, то для корректности этого вызова нужно, чтобы указатель не был null, указывал на корректное значение и был корректно выровнен — иными словами, чтобы указатель удовлетворял требованиям корректности для ссылки. Для того, чтобы отлавливать подобные ошибки, добавили для этого линт. Пока что он warn by default, но в будущем планируют поднять до deny by default.

▪️Для array::from_fn добавили гарантию, что переданная функция вызывается по порядку индексов. Актуально, если вызывается с некоторым замыканием, которое захватывает и мутирует стейт.

▪️Стабилизировали некоторые API:
🔸Cell::update — наверное, один из самых частых паттернов использования Cell.
🔸extract_if для HashMap и для HashSet.
🔸Default для сырых указателей (ожидаемо, null).
🔸Целая пачка геттеров для proc_macro::Span. Позволит писателям процедурных макросов делать более полезные диагностики.
🔸<[T]>::as_chunks, который разбивает переданный слайс на слайс массивов переданного const-параметром длины и остаток, который не влез, вместе с мутабельными вариациями, unchecked (UB, если остаток не пустой и длина нужных массивов равна нулю) и разбитием в обратную сторону.
🔸std::hint::select_unpredictable. Вызов select_unpredictable(cond, true_val, false_val) аналогичен if cond { true_val } else { false_val }, но сигнализирует оптимизатору, что процессор навряд ли сможет корректно предсказать условие (читай, просит использовать cmov-подобные операции вместо явных ветвлений). Используется, например, в реализации двоичного поиска в std.

Дополнительно некоторые API теперь доступны в const контекстах:
🔸NonNull::replace и <*mut T>::replace
🔸std::ptr::swap_nonoverlapping
🔸Пачка методов на Cell: replace, get, get_mut, from_mut и as_slice_of_cells.

▪️Cargo теперь умеет автоматически удалять старые файлы из кеша. Именно, на тяжёлых командах проверяет и удаляет скаченные файлы старше трёх месяцев и файлы из локальной файловой системы старше месяца. К сожалению, пороги не настраиваются.

▪️Cargo теперь использует zlib-rs для операций, требующих (за, рас)паковки gzip-файлов (например, cargo package). Для пользователя ничего не поменялось, но соответствующие операции стали работать быстрее. В pull request-е приводится в пример windows-bindgen, для которого cargo package работает на 60% (!) быстрее.

▪️Для rustdoc стабилизировали флаги, позволяющие указывать внешние программы, используемые для запуска док-тестов, требующих кросс-компиляции. Также ignore-*-атрибуты на док-тестах позволяют указывать цели компиляции, на которых док-тест не должен компилироваться.
👍147🤡2🤮1😐1
#prog #rust #rustreleasenotes

Вышла версия Rust 1.89.0! Как всегда, тут только избранные части, остальное — в детальных заметках о релизе.

▪️Стабилизировали вывод const generics в выражениях!

pub fn all_false<const LEN: usize>() -> [bool; LEN] {
[false; _]
}


К сожалению, использовать _ для const generics в сигнатурах всё ещё нельзя.

▪️Док-тесты теперь запускаются при кросс-компиляции.

▪️Атрибуты вида #![doc(test(attr(..)))] (которые добавляют #[attr(..)] ко всем док-тестам в модуле) теперь можно использовать везде, включая корень крейта.

▪️После фиксов для согласования с существующими компиляторами C/C++ теперь можно использовать i128 и u128 в extern "C" определениях, интероп корректно работает и, как следствие, improper_ctypes_definitions более не триггерится.

▪️Как я уже писал, продление времени жизни временных значений теперь работает и с кортежными конструкторами (кортежных структур и кортежных вариантов enum-ов).

▪️Стабилизированы интринсики (и связанные с ними target feature [1], [2]) из наборов инструкций AVX512, SHA512, SM3 и SM4 для x86-64.

▪️Макро-фрагмент в декларативных макросах без указанного типа теперь является ошибкой компиляции на всех edition.

▪️Результат вычисления format_args! теперь можно сохранять в переменных! 🎉

▪️std::array::IntoIter теперь реализовывает Default. Значение по умолчанию при этом является итератором, который ничего не возвращает. Полезно, когда нужно предоставить итерацию по опциональному массиву и при этом не хочется прокидывать Option в возвращаемое значение.

▪️LazyCell и LazyLock теперь реализуют DerefMut. Одной причиной использовать once_cell меньше.

▪️К слову о локах, для File теперь есть пачка методов ({, try_}lock{, _shared} и unlock) для работы с платформо-специфичными способами блокировки файлов. Напоминаю, что на Linux эти функции требуют кооперации: коду нужно явно указывать на то, что он обращает внимание на блокировки, а по умолчанию они ничего для сторонних процессов не делают.

▪️NonNull можно безопасно создать из ссылки, поскольку ссылки в Rust обязаны не быть null. Для этого есть реализация From<&T> for NonNull<T>. К сожалению, этот способ конвертации не очень очевиден, и на практике многие писали код с NonNull::new_unchecked, в котором ссылка приводилась к сырому указателю по месту вызова. Видимо, чтобы сделать безопасный способ конвертации более видимым и очевидным в исходниках, к NonNull добавили конструктор from_reffrom_mut для конвертации из &mut T). Также этому типу добавили методы для работы с provenance (expose_provenance, with_exposed_provenance, without_provenance), которые аналогичны таковым для сырых указателей, но принимают/возвращают NonZero<usize> вместо usize.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥32