#prog #rust #article
What does it take to ship Rust in safety-critical?
Статья про очень конкретные препятствия к использованию Rust в safety-critical системах.
Это именно препятствия, а не блоки — в этих областях Rust уже используется в проде.
What does it take to ship Rust in safety-critical?
Статья про очень конкретные препятствия к использованию Rust в safety-critical системах.
Это именно препятствия, а не блоки — в этих областях Rust уже используется в проде.
blog.rust-lang.org
What does it take to ship Rust in safety-critical? | Rust Blog
Empowering everyone to build reliable and efficient software.
🤔2
#prog #rust #article
Офигенно!
Rust's standard library on the GPU
Офигенно!
Rust's standard library on the GPU
GPU code can now use Rust's standard library. We share the implementation approach and what this unlocks for GPU programming.
This works because of our customhostcallframework. A hostcall is analogous to a syscall. A hostcall is a structured request from GPU code to the host CPU to perform something it cannot execute itself. You can think of it like a remote procedure call from the GPU to the host to achieve syscall-like functionality.
To end users, Rust's std APIs appear unchanged and act as one would expect.
❤12👍8
Блог*
В этом случае достаточно поменять тип поля irrelevant на UnconditionallyEqual<String> — и можно будет использовать стандартный #[derive(PartialEq)] для достижения требуемого функционала.
#prog #rust
Кое-что я при написании этого поста упустил. Именно, к хэшированию предъявляется логичное требование: если два значения равны, то их хэши также равны.
Кое-что я при написании этого поста упустил. Именно, к хэшированию предъявляется логичное требование: если два значения равны, то их хэши также равны.
UnconditionallyEqual в том виде, как я написал, не реализовывает Hash, поэтому #[derive(Hash)] работать не будет. Тем не менее, заставлять программиста вручную писать реализацию Hash из-за наличия одного такого поля — не лучший UX. Поэтому стоит написать реализацию Hash для UnconditionallyEqual. Из-за изложенного выше требования и того факта, что UnconditionallyEqual не влияет на операцию равенства, корректной реализацией будет noop:impl<T> Hash for UnconditionallyEqual<T> {
fn hash<H: Hasher>(&self, _state: &mut H) {}
}🍌2
#prog #rust
Одна из клёвых вещей, которые есть в #zig — это синтаксис для многострочных строковых литералов:
Большим преимуществом этого синтаксиса является тот факт, что он позволяет с лёгкостью сделать отступ для литерала целиком и при этом не добавлять этот отступ в сам литерал, а также требует минимального экранирования (всё до перевода строки — содержимое строки). Аналогичный код в, скажем, Rust, требует такой неприятной вещи:
и поддержки со стороны компилятора для того, чтобы экранирование перевода строки убирало не только сам перевод строки, но и предшествующие пробельные символы. Обратите внимание на то, что мне также пришлось экранировать кавычки и
Именно по этой причине есть аж два RFC (раз, два), которые добавляют новые виды строковых литералов для подобных целей (включения многострочного кода на другом языке). Тем не менее, в обсуждении второго один человек заметил (и тут дела приобретают #abnormalprogramming оборот), что имитировать многострочные литералы из Zig в Rust в некоторой мере можно уже сейчас. Именно, doc-комментарии уже на этапе лексирования заменяются на
Одна из клёвых вещей, которые есть в #zig — это синтаксис для многострочных строковых литералов:
const hello_world_in_c =
\\#include <stdio.h>
\\
\\int main(int argc, char **argv) {
\\ printf("hello world\n");
\\ return 0;
\\}
;
Большим преимуществом этого синтаксиса является тот факт, что он позволяет с лёгкостью сделать отступ для литерала целиком и при этом не добавлять этот отступ в сам литерал, а также требует минимального экранирования (всё до перевода строки — содержимое строки). Аналогичный код в, скажем, Rust, требует такой неприятной вещи:
let hello_world_in_c = "\
#include <stdio.h>\
\n\
\nint main(int argc, char **argv) {\
\n printf(\"hello world\\n\");\
\n return 0;\
\n}";
и поддержки со стороны компилятора для того, чтобы экранирование перевода строки убирало не только сам перевод строки, но и предшествующие пробельные символы. Обратите внимание на то, что мне также пришлось экранировать кавычки и
\n в коде. Сырые строковые литералы позволяют убрать экранирование, но не лишние отступы:let hello_world_in_c = r#"#include <stdio.h>
int main(int argc, char **argv) {
printf("hello world\n");
return 0;
}"#;
Именно по этой причине есть аж два RFC (раз, два), которые добавляют новые виды строковых литералов для подобных целей (включения многострочного кода на другом языке). Тем не менее, в обсуждении второго один человек заметил (и тут дела приобретают #abnormalprogramming оборот), что имитировать многострочные литералы из Zig в Rust в некоторой мере можно уже сейчас. Именно, doc-комментарии уже на этапе лексирования заменяются на
#[doc]-атрибуты, а потому можно макросом извлечь эти литералы из атрибутов и сконкатенировать:macro_rules! text {
(#[doc=$first_line:literal] $(#[doc=$lines:literal])*) => {
concat!($first_line, $("\n", $lines),*)
};
}
let hello_world_in_c = text! {
///#include <stdio.h>
///
///int main(int argc, char **argv) {
/// printf("hello world\n");
/// return 0;
///}
};GitHub
Propose code string literals by Diggsey · Pull Request #3450 · rust-lang/rfcs
Add a new syntax for multi-line string literals designed to contain code and play nicely with rustfmt.
Rendered
Rendered
🥴20❤2🤔2
#prog #rust #gamedev #amazingopensource (но пока сырое)
Revy is a proof-of-concept time-travel debugger for the Bevy game engine, built using Rerun.
Revy works by snapshotting diffs of the Bevy database every frame that are then logged into the Rerun database.
This allows you to inspect and visualize the state of the engine at any point in time, either in real-time or after the fact.
These recordings can then be shared to be replayed or e.g. attached to bug reports.
👍7🤔3❤1
#prog #rust #rustreleasenotes
Вышла версия Rust 1.93.0! Как обычно, тут только избранное, остальное только в детальных заметках о релизе.
(да, я выкладываю анонс с опозданием больше недели, и что ты мне сделаешь?)
В этот раз большинство изменений связаны со стандартной библиотекой.
▪️Немного печальное изменение: даже с установлением
▪️Компилятор теперь выдаёт предупреждение на #[repr(C)]-перечисления, у которых значения дискриминантов не умещаются в сишный int. Смешно, но до C23 у сишных
▪️Компилятор теперь выдаёт предупреждение на попытку вызвать метод на const-значении, который эксплуатирует внутреннюю изменяемость. Так как константы инлайнятся по месту использования, эти изменения не будут видны. Обычно это является следствием ошибки — использования
▪️Метод
▪️Стабилизированы API:
🔸assume_init_drop, assume_init_ref и assume_init_mut на
🔸
🔸
🔸методы для непроверяемых (и потому потенциально дающих UB) битовых сдвигов в обе стороны на примитивных числах, а на знаковых — ещё и unchecked_neg
🔸методы для перевода ссылок на слайсы в (опциональные, разумеется) ссылки на массивы и сырых указателей на слайсы в сырые указатели на массивы, включая мутабельные варианты
🔸pop_front_if и pop_back_if на
🔸std::fmt::from_fn для ad-hoc форматирования через переданную функцию. Позволяет избежать создания одноразовых типов, нужных только для форматирования
▪️Cargo теперь прокидывает значение конфигурации debug_assertions в билд-скрипты.
▪️Команда
▪️
Вышла версия Rust 1.93.0! Как обычно, тут только избранное, остальное только в детальных заметках о релизе.
(да, я выкладываю анонс с опозданием больше недели, и что ты мне сделаешь?)
В этот раз большинство изменений связаны со стандартной библиотекой.
▪️Немного печальное изменение: даже с установлением
#[global_allocator] std всё ещё может вызывать System аллокатор. По крайней мере, на core это не распространяется.▪️Компилятор теперь выдаёт предупреждение на #[repr(C)]-перечисления, у которых значения дискриминантов не умещаются в сишный int. Смешно, но до C23 у сишных
enum нельзя было указать численный тип и нельзя было использовать значения больше int для дискриминантов.▪️Компилятор теперь выдаёт предупреждение на попытку вызвать метод на const-значении, который эксплуатирует внутреннюю изменяемость. Так как константы инлайнятся по месту использования, эти изменения не будут видны. Обычно это является следствием ошибки — использования
const вместо static (и я, кстати, такую ошибку уже делал).▪️Метод
append на BTreeSet и BTreeMap теперь сохраняет ключ из исходной коллекции, если ключи считаются равными через ==. До этого перезаписывались и ключ, и значение.▪️Стабилизированы API:
🔸assume_init_drop, assume_init_ref и assume_init_mut на
MaybeUninit<T>🔸
write_{copy, clone}_of_slice на [MaybeUninit<T>]🔸
into_raw_parts на String и на Vec. Странно, что только сейчас, потому что обратные from_raw_parts методы были стабилизированы ещё в версии 1.0.🔸методы для непроверяемых (и потому потенциально дающих UB) битовых сдвигов в обе стороны на примитивных числах, а на знаковых — ещё и unchecked_neg
🔸методы для перевода ссылок на слайсы в (опциональные, разумеется) ссылки на массивы и сырых указателей на слайсы в сырые указатели на массивы, включая мутабельные варианты
🔸pop_front_if и pop_back_if на
VecDeque🔸std::fmt::from_fn для ad-hoc форматирования через переданную функцию. Позволяет избежать создания одноразовых типов, нужных только для форматирования
▪️Cargo теперь прокидывает значение конфигурации debug_assertions в билд-скрипты.
▪️Команда
cargo tree теперь позволяет использовать для форматирования записей в дереве длинные имена для элементов шаблонов. Например, следующие два вызова эквивалентны:cargo tree --format='{p} {f}'cargo tree --format='{package} {features}'▪️
cargo clean теперь может удалять артефакты компиляции из всего workspace-а целиком.👍8😁1
Random Rust Dev
Как заставить растовика задуматься над элементарной вещью?
Задайте ему вопрос: Какой тип у `0x12345_f32`?
Задайте ему вопрос: Какой тип у `0x12345_f32`?
#prog #rust
Но вообще ответ не вполне корректен.Тип числового литерала уточняется выводом типов, а конкретно i32 выбирается только в том случае, если ограничений на тип недостаточно, чтобы выбрать конкретный числовой тип (и лично я это считаю недостатком Rust )
Но вообще ответ не вполне корректен.
🤔7👍3
#prog #rust #article
В стандартной библиотеке Rust есть несколько ассоциативных контейнеров:
Возьмём в качестве примера HashMap::get:
Как видно из кода, тип для поиска (
В процессе поиска значения на хранимых ключах вызывается метод
Но у этого API есть недостаток. Именно, оно требует, чтобы предоставляемое значение было ссылкой и чтобы из ссылки на ключ можно было получить ссылку на
В короткой статье Borrowed tuple indexing for HashMap рассказывается, как с некоторым количеством бойлерплейта можно обойти это ограничение.
Для сравнения, hashbrown (поверх которого сделаны мапы в std) от подобных ограничений не страдает, поскольку там в API используется более гибкий трейт Equivalent:
В стандартной библиотеке Rust есть несколько ассоциативных контейнеров:
HashMap, HashSet, BTreeMap и BTreeSet. Часто на практике в качестве ключей в них хранятся строки — String. Требовать от пользователя для поиска значение типа String неудобно и чревато проблемами в производительности. Потому у этих структур данных есть API, позволяющие использовать для поиска ключи других, "похожие" на те, что хранятся в контейнере.Возьмём в качестве примера HashMap::get:
fn get<Q>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
Как видно из кода, тип для поиска (
Q) не обязан совпадать с типом хранимых ключей (K), но на K есть ограничение K: Borrow<Q>. Трейт Borrow выглядит таким образом:trait Borrow<Borrowed>
where
Borrowed: ?Sized,
{
fn borrow(&self) -> &Borrowed;
}
В процессе поиска значения на хранимых ключах вызывается метод
<K as Borrow<Q>>::borrow, и результат возвращаемого значения сравнивается со значением, переданным в get. Именно благодаря этому API (и реализациям в std, разумеется) коллекцию HashMap<String, Thing> можно индексировать значениями типа &str.Но у этого API есть недостаток. Именно, оно требует, чтобы предоставляемое значение было ссылкой и чтобы из ссылки на ключ можно было получить ссылку на
Q. Это ограничивает применимость API. Если, например, в мапе в качестве ключей хранятся (String, String), то логичный невладеющий эквивалент для индексации (&str, &str) не будет работать, потому что это кортеж ссылок, а не ссылка.В короткой статье Borrowed tuple indexing for HashMap рассказывается, как с некоторым количеством бойлерплейта можно обойти это ограничение.
Для сравнения, hashbrown (поверх которого сделаны мапы в std) от подобных ограничений не страдает, поскольку там в API используется более гибкий трейт Equivalent:
trait Equivalent<K>
where
K: ?Sized,
{
fn equivalent(&self, key: &K) -> bool;
}
👍10🤯3🤡1
Блог*
#prog #rust #article Офигенно! Rust's standard library on the GPU GPU code can now use Rust's standard library. We share the implementation approach and what this unlocks for GPU programming. This works because of our custom hostcall framework. A hostcall…
Vectorware
Async/await on the GPU
GPU code can now use Rust's async/await. We share the reasons why and what this unlocks for GPU programming.
🔥8😱3❤1😭1
#prog #rust #article
Making cargo-semver-checks faster - Google Summer of Code 2025
Making cargo-semver-checks faster - Google Summer of Code 2025
Overall, I reduced the typical runtime on very large crates down to ~2s from ~8s - nearly an 80% speedup - without compromising performance on smaller crates. Along the way I reduced test time from ~7min to ~1min.
❤6
#rust
Rust debugging survey 2026
Rust debugging survey 2026
You can fill out the survey here.
Filling the survey should take you approximately 5 minutes, and the survey is fully anonymous. We will accept submissions until Friday, March 13th, 2026.
blog.rust-lang.org
Rust debugging survey 2026 | Rust Blog
Empowering everyone to build reliable and efficient software.
🤔2
#prog #rust #rustreleasenotes
Вышла версия Rust 1.94.0! Как всегда, тут только то, что интересно мне, остальное в детальных заметках о релизе.
▪️Атрибуты для линта
▪️Касты между сырыми указателями на unsized типы с указанием лайфтаймов теперь требуют, чтобы предыдущий тип жил не меньше нового. Или, иными словами, каст из
▪️Стабилизировали array_windows! Украду пример прямо из блогпоста:
Благодаря выводу типов ещё и размер, как правило, не нужно указывать на методе явно.
▪️
▪️Для
▪️cargo теперь использует для парсинга манифестов наконец-то вышедшую версию TOML 1.1. С практической точки зрения это главным образом означает, что inline-таблицы можно писать на нескольких строках и с запятой после — или, иными словами, зависимость в Cargo.toml с большим количеством фичей теперь можно писать среди остальных и не пихать её в одну длинную строку.
▪️Ещё стабилизация: cargo теперь позволяет разбить конфиг на несколько файлов и объединить их при помощи top-level ключа
Вышла версия Rust 1.94.0! Как всегда, тут только то, что интересно мне, остальное в детальных заметках о релизе.
▪️Атрибуты для линта
dead_code (allow/warn/deny/expect) на трейтах и их частях теперь наследуются impl-ами:#[allow(dead_code)]
trait Foo {
const FOO: u32;
}
impl Foo for u32 {
const FOO: u32 = roundtrip(0);
}
// нет предупреждения о мёртвом коде
const fn roundtrip(x: u32) -> u32 {
x
}
▪️Касты между сырыми указателями на unsized типы с указанием лайфтаймов теперь требуют, чтобы предыдущий тип жил не меньше нового. Или, иными словами, каст из
*mut dyn Trait + 'a в *mut dyn Trait + 'b теперь требует 'a: 'b▪️Стабилизировали array_windows! Украду пример прямо из блогпоста:
For example, part of one 2016 Advent of Code puzzle is looking for ABBA patterns: "two different characters followed by the reverse of that pair, such as xyyx or abba." If we assume only ASCII characters, that could be written by sweeping windows of the byte slice like this:
fn has_abba(s: &str) -> bool {
s.as_bytes()
.array_windows()
.any(|[a1, b1, b2, a2]|
(a1 != b1)
&& (a1 == a2)
&& (b1 == b2)
)
}Благодаря выводу типов ещё и размер, как правило, не нужно указывать на методе явно.
▪️
LazyCell и LazyLock обзавелись методами get, get_mut и force_mut.▪️Для
iter::Peekable добавили методы next_if_map и next_if_map_mut. Пример из документации:let mut iter = "125 GOTO 10".chars().peekable();
let mut line_num = 0_u32;
while let Some(digit) = iter.next_if_map(|c| c.to_digit(10).ok_or(c)) {
line_num = line_num * 10 + digit;
}
assert_eq!(line_num, 125);
assert_eq!(iter.collect::<String>(), " GOTO 10");
▪️cargo теперь использует для парсинга манифестов наконец-то вышедшую версию TOML 1.1. С практической точки зрения это главным образом означает, что inline-таблицы можно писать на нескольких строках и с запятой после — или, иными словами, зависимость в Cargo.toml с большим количеством фичей теперь можно писать среди остальных и не пихать её в одну длинную строку.
▪️Ещё стабилизация: cargo теперь позволяет разбить конфиг на несколько файлов и объединить их при помощи top-level ключа
include (документация).👍5
#prog #rust #article
How to stop fighting with coherence and start writing context-generic trait impls
Транскрипт выступления, если что. Мне помогло понять, что же всё-таки такое context-generic programming, как это называет автор
How to stop fighting with coherence and start writing context-generic trait impls
Транскрипт выступления, если что. Мне помогло понять, что же всё-таки такое context-generic programming, как это называет автор
🔥4
#prog #rust #article
Под капотом Beetroot: как я написал менеджер буфера обмена на Tauri v2 и Rust с установщиком 6 МБ
Интерфейс на WebView, да
Под капотом Beetroot: как я написал менеджер буфера обмена на Tauri v2 и Rust с установщиком 6 МБ
Интерфейс на WebView, да
🤔6👍2
#prog #rust #article
symbolic derivatives and the rust rewrite of RE#
Растовая версия очень быстрого движка для регулярных выражений, который поддерживает, помимо прочего, конъюкцию (пересечение результатов подвыражений), отрицание и lookahead и при этом работает за линейное от входных данных время. По производительности на выражениях с большим количеством состояний обгоняет regex, особенно для поиска без учёта регистра.
Теория, поддерживающая этот движок — это развитие идей Brzozowski, но вместо того, чтобы считать производную от регулярного выражения для разных символов и потом объединять их по классам эквивалентности, новый подход считает т. н. символическую производную — производную для всех возможных входных символов сразу.
Из-за того, что данный подход поддерживает конъюкцию, движок может работать на байтах и при этом поддерживать UTF-8 просто за счёт добавления правила, которое ограничивает вход до валидных UTF-8 последовательностей:
Пример кода:
symbolic derivatives and the rust rewrite of RE#
Растовая версия очень быстрого движка для регулярных выражений, который поддерживает, помимо прочего, конъюкцию (пересечение результатов подвыражений), отрицание и lookahead и при этом работает за линейное от входных данных время. По производительности на выражениях с большим количеством состояний обгоняет regex, особенно для поиска без учёта регистра.
Теория, поддерживающая этот движок — это развитие идей Brzozowski, но вместо того, чтобы считать производную от регулярного выражения для разных символов и потом объединять их по классам эквивалентности, новый подход считает т. н. символическую производную — производную для всех возможных входных символов сразу.
Из-за того, что данный подход поддерживает конъюкцию, движок может работать на байтах и при этом поддерживать UTF-8 просто за счёт добавления правила, которое ограничивает вход до валидных UTF-8 последовательностей:
// \p{utf8} expands to:
// ([\x00-\x7F]
// | [\xC0-\xDF][\x80-\xBF]
// | [\xE0-\xEF][\x80-\xBF]{2}
// | [\xF0-\xF7][\x80-\xBF]{3})*Пример кода:
use resharp::Regex;
// basic matching
let re = Regex::new(r"hello.*world").unwrap();
assert!(re.is_match("hello beautiful world"));
// intersection: contains both "cat" and "dog", 5-15 chars
let re = Regex::new(r"_*cat_*&_*dog_*&_{5,15}").unwrap();
// complement: does not contain "1"
let re = Regex::new(r"~(_*1_*)").unwrap();
ian erik varatalu
symbolic derivatives and the rust rewrite of RE# | ian erik varatalu
👍15🔥8❤2
#prog #rust #rustlib #article
🦀Building Rust Procedural Macros Without quote!: Introducing zyn
🦀Building Rust Procedural Macros Without quote!: Introducing zyn
I've been writing proc macros for a while now. Derive macros for internal tools, attribute macros for instrumentation. And every time, the same two problems:quote!doesn't compose (you end up passingTokenStreamfragments through five layers of helper functions and writing hundreds ofletstatements), and debugging generated code meanscargo expandand then squinting at unformatted token output hoping something jumps out.
Because of this I ended up writing the same helper methods, composite AST parsing and tokenizing types, extractors etc. I would have to copy these from project to project as needed, and eventually just decided to publish a crate so I never have to do it again.
So I built zyn — a proc macro framework with a template language, composable components, and compile-time diagnostics.
I wrote the debug system after spending two days on a bug where a generated impl block was missing a lifetime bound. cargo expand spat out 400 lines of tokens and I couldn't find it, so I built a debug system.
🤔5❤1👍1🤡1
#prog #rust #rustlib
bnum — библиотека для работы с обобщёнными числами, для которых на уровне типов зафиксированы: знаковость, число байт под хранение, битовая ширина и поведение при переполнении.
bnum — библиотека для работы с обобщёнными числами, для которых на уровне типов зафиксированы: знаковость, число байт под хранение, битовая ширина и поведение при переполнении.
use bnum::prelude::*;
// imports common use items
// including the `Integer` type and the `n!` macro
// say we want to write a polynomial function
// which takes any unsigned or signed integer
// of any bit width and with any overflow behaviour
// for example, the polynomial could be p(x) = 2x^3 + 3x^2 + 5x + 7
fn p<const S: bool, const N: usize, const B: usize, const OM: u8>(x: Integer<S, N, B, OM>) -> Integer<S, N, B, OM> {
n!(2)*x.pow(3) + n!(3)*x.pow(2) + n!(5)*x + n!(7)
// type inference means we don't need to specify the width of the integers in the n! macro
}
// 2*10^3 + 3*10^2 + 5*10 + 7 = 2357
assert_eq!(p(n!(10U256)), n!(2357));
// evaluates p(10) as a 256-bit unsigned integer
type U24w = t!(U24w);
// 24-bit unsigned integer with wrapping arithmetic
type I1044s = t!(I1044s);
// 1044-bit signed integer with saturating arithmetic
type U753p = t!(U753p);
// 753-bit unsigned integer that panics on arithmetic overflow
let a = p(U24w::MAX); // result wraps around and doesn't panic
let b = p(I1044s::MAX); // result is too large to be represented by the type, so saturates to I044s::MAX
// let c = p(U753p::MAX); // this would result in panic due to overflow
🫡9🤔2❤1