1.96K subscribers
3.85K photos
147 videos
15 files
4.02K links
Блог со звёздочкой.

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

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

Вышла версия Rust 1.75.0! Как всегда, тут только избранные моменты, а все изменения отдельно (BTW ссылка теперь ведёт не RELEASES.md, а на отдельную страницу).

▪️Само значимое изменение: теперь возможно использовать impl Trait в возвращаемых типах методов трейта и, как следствие, эквивалентные методам с RPIT async-методы. Однако текущая реализация ИМЕЕТ СУЩЕСТВЕННЫЕ ОГРАНИЧЕНИЯ, так что это не рекомендуется к использованию для публичного API. Подробнее об ограничениях (и костылях) — тут.

А, и ещё RPIT в трейтах захватывает времена жизни иначе, чем RPIT в inherent методах и свободных функциях. Подробнее тут (осторожно, может вызвать головную боль). Этот способ планируют сделать для всех impl Trait типов в возвращаемых позициях в edition 2024.

▪️В const fn сейчас нельзя использовать &mut-ссылки. Проверка для этого в компиляторе была, однако, излишне строгой. Теперь её ослабили и в const fn можно манипулировать fn pointer-ами, у которых есть мутабельные ссылки в аргументах. Но вызывать их по прежнему нельзя.

▪️Ещё изменение касательно const fn: вычисления в них с нарушением выравнивания теперь приводят к ошибке компиляции. Раньше на это был deny default линт, который можно было явно отключить.

▪️И ещё касательно невыровненных ссылок: компилятор теперь корректно ловит создание ссылок на unsized поля в #[repr(packed)] структурах.

▪️В язык и std добавлено несколько новых гарантий, о которых я упоминал:
🔸char имеет одинаковый с u32 размер и выравнивание
🔸null всегда имеет нулевой адрес
🔸для некоторых типов Option<T> вызов transmute на памяти, забитой нулями, валиден и гарантированно даёт None

А также:
🔸задокументированно, когда атомарные load валидны на readonly памяти.

▪️match теперь может матчиться на usize/isize значениях с проверкой полноты покрытия без _, а также может матчиться по (частично) перемещённым значениям, если паттерны не смотрят на значение. Это поведение консистентно с поведением let _ = expr;.

▪️Метод std::split_inclusive теперь возвращает итератор, который корректно реализует DoubleEndedIterator в том плане, что возвращает одни и те же кусочки строки при итерации как в прямом, так и в обратном порядке.

▪️Стабилизированы некоторые новые API, в частности:
🔸Конструкторы ссылок на Atomic-типы из сырых указателей (unsafe, разумеется)
🔸Пачку API (включая платформо-специфичные) для манипуляции временны́ми атрибутами файлов
🔸Битовые операции над IP-адресами
🔸Option::as{, _mut}_slice.
🔸Пачку методов для сырых указателей с указаниями смещений в байтах, а не в размерах указываемого типа.

Также в const контексте теперь можно вызывать:
🔸
MaybeUninit::assume_init_read
🔸
MaybeUninit::zeroed
🔸
mem::discriminant
🔸
mem::zeroed

Насчёт Option::as_slice у вас мог возникнуть резонный вопрос, чем этот метод лучше opt_value.map_or(&[], std::slice::from_ref). Дело в том, что такой прямолинейный метод будет использовать некий посторонний адрес для случая, когда значение является None, а новый метод старается по возможности вернуть ссылку на слайс с адресом, совпадающим с адресом исходного опционального значения.

▪️Некоторые улучшения rustdoc:
🔸Генерируется предупреждение, когда трейт не является object safe.
🔸Атрибут #[repr(transparent)] скрывается, если поле, над которым тип является transparent, является приватным (поскольку в этом случае repr наверняка является деталью реализации)
🔸Для C-like (с вариантами без полей) enum теперь показываются дискриминанты вариантов.

▪️Компилятор (конкретно librustc_driver) теперь оптимизируется при помощи BOLT — специального инструмента для оптимизации скомпилированных программ, который переставляет инструкции для лучшей утилизации кеша. Даёт прибавку в скорости.

▪️Маленькие функции теперь автоматически инлайнятся через границу крейтов. Дало большой выигрыш и по primary, и по secondary бенчмаркам — причём для времени компиляции даже лучше, чем для рантайм-бенчмарков.
🔥8❤‍🔥3👍2🎉1
#prog #rust #rustreleasenotes

Вышла версия Rust 1.76.0... Почти месяц назад. В свою защиту могу сказать, что изменения довольно минорные и потому релиз не обязывает к апдейту. Как обычно, тут только отдельные моменты, целиком в release notes.

▪️Единственная существенная вещь: исправлены ошибки при работе с unsized #[repr(packed(N))] структурами, где N > 1. Именно — неправильный расчёт смещения до unsized поля и неправильный подсчёт размера и выравнивания в рантайме.

▪️Задокументированы гарантии насчёт совместимости по ABI. Они были и раньше, просто теперь записаны.

▪️dbg! теперь печатает и колонку места расположения

▪️Исправлен старый баг с некорректным округлением чисел при форматировании в научной нотации с ограниченным числом десятичных знаков после запятой.

▪️Опция create на File теперь работает корректно со скрытыми файлами на Windows вместо того, чтобы тихо падать.

▪️Vec::from_iter теперь переиспользует аллокацию с ещё несколькими итераторами, которые оборачивают vec::IntoIter.

▪️Реализации Debug для RwLockReadGuard и RwLockWriteGuard теперь не требуют Sized на типе внутри лока.

▪️Как я уже писал, IMPLIED_BOUNDS_ENTAILMENT теперь является ошибкой компиляции.

▪️Пачка новых API:

🔸Option::inspect, Result::{inspect, inspect_err} (с семантикой, аналогичной Iterator::inspect)
🔸{Arc, Rc}::unwrap_or_clone
🔸type_name_of_val
🔸ptr::{from_ref, from_mut}. Имеет смысл по тем же причинам, почему вместо as-кастов на числах используются from/into.
🔸ptr::addr_eq — потому что сравнение указателей вместе с метаданными почти всегда не то, что нужно
🔸std::hash::{DefaultHasher, RandomState}. Строго говоря, не новое API, но раньше это было доступно только через std::collections::hash_map.
👍81
#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