1.83K subscribers
3.23K photos
126 videos
15 files
3.51K links
Блог со звёздочкой.

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

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
Блог*
I love Rust for letting me write self-describing code trait Т { type T; const TT: (Self::T, Self::T); } trait ТТ<T> { const Т: T; } trait Sеlf { const Sеlf: Self; } const fn t<T: Т<T = isize>>() -> isize { impl<T: ТТ<T>> Sеlf for T…
#prog #rust #моё

Что ж, наверное, стоит объяснить, а что это вообще за код такой.

Странно выглядят фрагменты вроде

trait Sеlf {
const Sеlf: Self;
}

и

const fn t<T: Т<T = isize>>

, поскольку они вроде бы не должны компилироваться из-за совпадающих имён. Дело в том, что я использовал в Sеlf кириллическую е (и в имени трейта, и в имени ассоциированного типа), а в именах трейтов Т и ТТ — соответственно кириллическую Т. Это разрешено с версии Rust 1.53. При этом компилятор выдаёт (помимо всего прочего) вот такое предупреждение на этот код:

warning: the usage of Script Group `Cyrillic` in this crate consists solely of mixed script confusables
--> src/lib.rs:1:7
|
1 | trait Т {
| ^
|
= note: the usage includes 'Т' (U+0422), 'е' (U+0435)
= note: please recheck to make sure their usages are indeed what you want
= note: `#[warn(mixed_script_confusables)]` on by default

И также указывает на пары идентификаторов, которые могут быть перепутаны. Полезный компилятор, как ни крути.

Если переписать код, чтобы использовать лишь ASCII имена (и заодно привести его под принятые соглашения о наименованиях), то получится следующее:

trait PairValue {
type T;
const PAIR_VALUE: (Self::T, Self::T);
}

trait GenericValue<T> {
const GENERIC_VALUE: T;
}

trait SelfValue {
const SELF_VALUE: Self;
}

const fn t<T: PairValue<T = isize>>() -> isize {
impl<T: GenericValue<T>> SelfValue for T {
const SELF_VALUE: Self = T::GENERIC_VALUE;
}
T::PAIR_VALUE.0 * T::PAIR_VALUE.1 + 20
}

const fn self_describing<T: SelfValue>() -> T {
impl<T: PairValue<T = isize>> GenericValue<isize> for T {
const GENERIC_VALUE: isize = t::<T>();
}
T::SELF_VALUE
}

enum The {
Answer = {
impl PairValue for isize {
type T = isize;
const PAIR_VALUE: (isize, isize) = (2, 11);
}
self_describing()
},
}
const _ASSERT: [(); 42] = [(); The::Answer as usize];

Вроде выглядит немного получше, но всё ещё запутанно. В Rust, как известно, (почти) всё является выражением. Различного рода top level определения (называемые item в reference) также могут быть использованы, как выражения, и при этом возвращают (). Очень много куда можно воткнуть impl-блок — в частности, внутри функции и внутри выражения. В том числе внутри выражения для дискриминанта варианта enum. Разумеется, при этом применяются обычные правила видимости: нельзя реализовать трейт, если трейт или тип, для которого они определяются, не видны в текущей области видимости, но сами impl-ы трейтов видны всюду, где виден сам трейт. Тут эти правила видимости, впрочем, не мешают.

Если вынести на верхний уровень impl-блоки (как я бы и сделал в реальном коде) и немного переупорядочить для ясности, то получится вот это:

trait PairValue {
type T;
const PAIR_VALUE: (Self::T, Self::T);
}

impl PairValue for isize {
type T = isize;
const PAIR_VALUE: (isize, isize) = (2, 11);
}

trait GenericValue<T> {
const GENERIC_VALUE: T;
}

const fn t<T: PairValue<T = isize>>() -> isize {
T::PAIR_VALUE.0 * T::PAIR_VALUE.1 + 20
}

impl<T: PairValue<T = isize>> GenericValue<isize> for T {
const GENERIC_VALUE: isize = t::<T>();
}

trait SelfValue {
const SELF_VALUE: Self;
}

impl<T: GenericValue<T>> SelfValue for T {
const SELF_VALUE: Self = T::GENERIC_VALUE;
}

const fn self_describing<T: SelfValue>() -> T {
T::SELF_VALUE
}

enum The {
Answer = self_describing(),
}

const _ASSERT: [(); 42] = [(); The::Answer as usize];

В последней строчке мы находим ответ к загадке этого кода строчку, которая фактически является ассертом времени компиляции на то, что численное значение The::Answer является 42. Откуда берётся это значение? Выше Answer присваивается значение вызова определённой парой строк выше функции self_describing (кстати, использовать трейты на обобщённых параметрах в const fn стало возможным с версии 1.61). Но это обобщённая функция — так какой же тип там выводится?
1😐1
Ну а теперь серьёзно.

#prog #rust #rustreleasenotes

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

▪️Новый вариант опции атрибута #[link] — конкретно kind="raw-dylib" — позволяет на Windows линковаться с динамическими библиотеками, не требуя их наличия во время компиляции. Кросс-компиляция стала немного проще. А, и ещё атрибуты для импорта и экспорта символом из/в DLL, но я недостаточно в этом разбираюсь, чтобы понять разницу.

▪️ Уже имеющиеся варианты ABI обзавелись "*-unwind" вариантами (полный список). Как нетрудно догадаться, паники и исключения, вызывающую раскрутку стека, могут проходить границу между языками. В частности, это означает, что исключения из C++ могут проходить через фреймы Rust-кода, а растовая паника может проходить через фреймы C++-кода. Ловить раскрутку стека, впрочем, можно лишь в фреймах того языка, в котором она была начата.

Это изменение сделает более удобным разработку проектов, смешивающих код на Rust с кодом на других языках. Не *-unwind варианты ABI, как и раньше, абортят процесс, если раскрутка стека пытается пройти по FFI.

▪️Ещё одно изменение, связанное с паниками, но уже исключительно в рамках Rust. Ранее паника во время паники приводила к немедленному аборту процесса. Теперь это ограничение несколько ослабили: паника во время паники не приводит к аборту при условии, что она перехватывается на границе реализации drop. Иными словами, паники внутри деструкторов — которые сами могут быть вызваны из-за паники — могут быть перехвачены и корректно обработаны, не приводя к немедленному прекращению работы.

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

▪️Стало возможным включать в бинарники скрипты для визуализации значений пользовательских типов в отладчиках. Без этих скриптов в отладчике значение, скажем, Vec<i32> выглядело бы не как последовательность чисел, а как набор из адреса, длины и ёмкости — что, очевидно, не слишком-то полезно. Такими скриптами для визуализации укомплектовывается std. Новый механизм позволяет использовать этот же механизм и пользователям Rust.

▪️Обновлена версия MUSL, используемая для *-linux-musl. Главным образом это означает, что 64-битная версия time_t, позволяющая избежать проблемы 2038 года, используется безусловно для всех систем, включая 32-битные.

▪️Макрос format_args! (на который опираются куча макросов как в std, так и вне её) теперь инлайнит форматируемые значения в форматные строки, если они являются литералами или вложенными format_args!. Если наглядно, то следующие пары вызовов теперь эквивалентны даже не на уровне бинарного кода, а ещё на уровне HIR:

println!("Hello, {}!", "World");
println!("Hello, World!");

println!("[info] {}", format_args!("error"));
println!("[info] error");

println!("[{}] {}", status, format_args!("error: {}", msg));
println!("[{}] error: {}", status, msg);

println!("{} + {} = {}", 1, 2, 1 + 2);
println!("1 + 2 = {}", 1 + 2);

Из того, на что на это влияет:

* в этих случаях в результирующем коде теперь меньше виртуальных вызовов fmt-методов;
* как следствие, макросы вроде log::info! теперь не вредят производительности из-за пробрасывания аргументов через format_args!;
* core::fmt::Arguments::as_str() теперь чаще возвращает Some(_);
* в бинаре теперь может сильно распухнуть секция .text из-за большего количества уникальных строк — особенно, если в коде активно используется макрос dbg!. Не то, чтобы это было большой проблемой, но может выйти боком, если проект компилируется, скажем, под embedded, где каждый байт на счету.

▪️Несколько изменений докатились до стейбла: улучшения расчёта раскладки типов, фикс расчёта приватности для Self-конструктора у кортежных структур, документация для const-инициализации thread local статиков, стабилизация BuildHasher::hash_one.
👍8🎉21👎1
#prog #rust #rustreleasenotes

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

▪️Офигенно полезная вещь: компилятор теперь говорит о том, что имена не определены из-за того, что определяющий их код находится под неактивным #[cfg].

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

▪️Несколько линтов из Clippy втащили в компилятор, а именно:

🔹undropped_manually_drops (warn по умолчанию) — попытка явно дропнуть ManuallyDrop.
🔹invalid_utf8_in_unchecked — разделив при этом на две:
🔸invalid_utf8_in_unchecked (deny по умолчанию) при вызове std::str::from_utf8_unchecked{, _mut} на невалидном UTF-8 — это всегда неопределённое поведение
🔸invalid_from_utf8 (warn по умолчанию) при вызове std::str::from_utf8{, _mut} на невалидном UTF-8 — такой вызов всегда возвращает ошибку
🔹cmp_nan как invalid_nan_comparisons (warn по умолчанию) — явное сравнение с NaN (такое сравнение всегда возвращает ложь)
🔹cast_ref_to_mut — при кастах из &T в &mut T. allow по умолчанию, но исключительно из-за наличия false positive, к следующему релизу планируют сделать уже deny по умолчанию.

▪️Для указания трейт-объектов теперь не нужно выписывать ассоциированные типы, на которых есть ограничение Self: Sized. Это консистентно с where Self: Sized на методах, наличие которых не влияет на object safety, но которые нельзя вызвать на трейт-объектах. Отмечу, что указывать остальные типы для трейт-объектов всё также надо.

▪️Отправляющая половинка mpsc-канала из std наконец-то реализует Sync.

▪️Уточнено поведение HashSet::insert: если ключ уже есть в множестве, то он не заменяется, а переданный ключ дропается.

▪️Как я уже говорил, select_nth_unstable теперь имеет реальную задокументированную линейную сложность.

▪️Опять-таки, как я уже рассказывал, ptr_eq на счётчиках ссылок теперь сравнивает лишь адреса.

▪️Стабилизирован impl TryFrom<&OsStr> for &str

▪️В const-контексте теперь можно использовать CStr::from_bytes_with_nul, CStr::to_bytes, CStr::to_bytes_with_nul и CStr::to_str.
👍8🔥2
#prog #rust #rustreleasenotes

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

В целом довольно минорный релиз, сильных причин обновлять нету.

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

▪️В макросах теперь можно вставлять метапеременные типа block после ключевых слов try и async. Пример кода, который не работал раньше, но работает теперь (результат раскрытия второго макроса, конечно, всё ещё требует активации фичи):


macro_rules! create_async {
($body:block) => {
async $body
};
}

macro_rules! create_try {
($body:block) => {
try $body
};
}

▪️Как я уже писал, компилятор теперь ловит безусловную рекурсию в дропах.

▪️Как и обещано, линт cast_ref_to_mut (на касты из &T в &mut T — в том числе и не напрямую) теперь deny по умолчанию.

▪️Задокументирована текущая (v0) используемая rustc версия манглинга имён.

▪️Строку теперь можно индексировать парами core::ops::Bound

▪️Немного поменяли формат сообщений паник по умолчанию для assert! и assert_eq!/assert_ne!. Примеры:

Код:

fn main() {
let file = "ferris.txt";
panic!("oh no! {file:?} not found!");
}

До:

thread 'main' panicked at 'oh no! "ferris.txt" not found!', src/main.rs:3:5

После:

thread 'main' panicked at src/main.rs:3:5:
oh no! "ferris.txt" not found!

Код:

fn main() {
assert_eq!("🦀", "🐟", "ferris is not a fish");
}

До:

thread 'main' panicked at 'assertion failed: `(left == right)`
left: `"🦀"`,
right: `"🐟"`: ferris is not a fish', src/main.rs:2:5

После:

thread 'main' panicked at src/main.rs:2:5:
assertion `left == right` failed: ferris is not a fish
left: "🦀"
right: "🐟"

По моему, стало более читаемо.

▪️Для LocalKey<Cell<T>> и LocalKey<RefCell<T>> (LocalKey — тип, в который заворачиваются значения в макросе thread_local!) добавили несколько методов для прямой манипуляции с значениями, без использования общего with. Мало того, что это позволяет сделать код нагляднее, так ещё и позволяет в некоторых случаях избежать инициализации thread local переменной значением, которое будет тут же перезаписано. При этом в общности API не теряет, поскольку на практике почти всегда из-за требований внутренней изменяемости значение и так было завёрнуто в Cell или RefCell.

▪️Для примитивных беззнаковых числовых типов доступны методы div_ceil (деление с округлением вверх, наконец-то!), next_multiple_of и checked_next_multiple_of. Все из них работают в cosnt-контексте.

▪️Ещё в const-контексте теперь можно создавать слабые ссылки (и Arc-сорта тоже) и переводить NonNull в ссылку.
👍91
#prog #rust #rustreleasenotes

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

▪️Компилятор теперь позволяет использовать в непрозрачных возвращаемых типах проекции из Self, в которые входят лайфтаймы не из сигнатуры функции. На практике это означает, что функции с impl Trait и async-функции, в возвращаемом типе которых есть Self, теперь работают всегда, а не наталкиваются на произвольные ограничения компилятора. Подробнее вместе с примерами кода, который не компилировался раньше и стал приниматься сейчас, смотри в соответствующем PR.

▪️Ранее замыкания, которые захватывали по ссылке поля #[repr(packed)] структур, захватывали их по разному в зависимости от того, являлась ли поле корректно выровненным или нет. Из-за этого смена типа поля в packed структуре — даже не того, что было захвачено! — могло привести к смене раскладке замыкания и, как следствие, изменению поведения из-за смены порядка дропа полей. В этой версии компилятора решили избавиться от столь странного поведения: теперь поля packed структур захватываются по ссылке одинаково вне зависимости от того, насколько выровнены поля.

▪️Насчёт repr: теперь можно явно писать #[repr(Rust)]

▪️Поменяли линты о приватных определениях внутри публичных определений (например, публичная функция, возвращающая приватный тип). Старый линт (private_in_public) страдал от того, что принимал во внимание исключительно номинальную видимость — ту, которая ставится перед именем (тип pub(in foo)). Из-за этого линт имел формально простое, но довольно неинтуитивное поведение, причём ещё и неполное — из-за вывода типов приватный тип мог утечь способом, который старый линт не ловил. Теперь его заменили пачкой новых линтов, которые работают на эффективной видимости, т. е. принимая во внимание видимость объемлющих определений. Это даёт более полезное для людей поведение. Подробнее в соответствующем RFC.

▪️Насчёт линтов: задавать их теперь можно через секцию в манифесте Cargo.toml. С учётом того, что эта секция наследуется в workspace, это позволяет убедиться, что в группе связанных проектов используется идентичный набор глобальных линтов, без необходимости синхронизировать их руками.

▪️cargo clean теперь поддерживает --dry-run.

▪️Стабилизировали пачку API в стандартной библиотеке, в том числе:
🔸core::num::Saturating — адаптер для примитивных числовых типов, реализующий насыщающую семантику для арифметических операций:
    assert_eq!(Saturating(u32::MAX) + Saturating(1), Saturating(u32::MAX));
🔸пачку методов для перевода в/из байты для OsStr{, ing}: as_encoded_bytes/from_encoded_bytes_unchecked. Ранее это было возможно только на Unix-системах через std::os::unix::ffi::OsStrExt.
🔸Реализации From из ссылок (обоих видов) на массивы в векторы и из массивов в {Arc, Rc}<[T]>.

Дополнительно следующие API теперь могут быть использованы в константном контексте:
🔸core::mem::transmute_copy
🔸str::is_ascii, [u8]::is_ascii 😙👌

▪️Как я уже писал, Cell::swap теперь паникует на частично перекрывающихся значениях.

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

▪️В сгенерированной rustdoc документации теперь можно искать с использованием типовых параметров.
Для примера, это означает, что Option::or можно найти по запросу

option<T>, option<T> -> option<T>
👍8❤‍🔥3
#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
#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