Size: a a a

2022 January 15
Блог*
#prog #rust #article

Writing Non-Trivial Macros in Rust — полезная статья о том, как поэтапно писать на Rust (относительно) сложные декларативные макросы. Ссылается на The Little Book of Rust Macros, которая сама по себе достойна прочтения.
источник
2022 January 16
Блог*
#prog #rust #моё

В крейте pulldown-cmark есть структура Options, в которой хранятся опции для конфигурирования поведения парсера. Эта структура перемещается внутрь парсера при создании и в процессе парсинга флаги оттуда только считываются, но никогда не меняется. Звучит, как идеальный кандидат для вынесения на уровень типов.

Я решил провести эксперимент: поменять определение Options с этого:

bitflags::bitflags! {
   pub struct Options: u32 {
       const ENABLE_TABLES = 1 << 1;
       const ENABLE_FOOTNOTES = 1 << 2;
       const ENABLE_STRIKETHROUGH = 1 << 3;
       const ENABLE_TASKLISTS = 1 << 4;
       const ENABLE_SMART_PUNCTUATION = 1 << 5;
       const ENABLE_HEADING_ATTRIBUTES = 1 << 6;
   }
}

на это:

pub struct Options<
   const ENABLE_TABLES: bool,
   const ENABLE_FOOTNOTES: bool,
   const ENABLE_STRIKETHROUGH: bool,
   const ENABLE_TASKLISTS: bool,
   const ENABLE_SMART_PUNCTUATION: bool,
   const ENABLE_HEADING_ATTRIBUTES: bool,
>(());

Там, где в коде был вызов, скажем, options.contains(Options::ENABLE_TABLES), в моём варианте стал вызов options.enable_tables() — метод, который просто возвращает значение параметра ENABLE_TABLES. Имея на руках доказуемо константные значения, компилятор может убрать из кода if-ы и вместе с ними лишний код, что должно снизить объём генерируемого кода для каждого варианта парсинга и повысить производительность за счёт отсутствия ветвлений.

Я запустил бенчмарки (кстати, разрабы молодцы, они используют criterion), ожидая увидеть увеличение ПЕРФОРМАНСА. И я действительно его увидел... На бенчмарках, число которых можно было пересчитать по пальцам одной руки. Во всех остальных бенмарках производительность либо не поменялась, либо — что было куда чаще — статистически значимо ухудшилась. 😒

То ли я неверно бенчмаркаю (что вполне может быть, ибо на фоне у меня таки висел VS Code), то ли я чего-то не знаю об оптимизирующих компиляторах. Короче, я разочарован, опечален и сконфужен.
источник
Блог*
Антон:
— Rust — простой язык, чего вы?

Тоже Антон:
<List as AsStrList<{ n_digits(<N as NumericValue>::VALUE) }>>::LIST
источник
2022 January 17
Блог*
Обновил прошивку своего чипа
источник
Блог*
Переслано от S Jack
источник
2022 January 18
Блог*
#prog #meme
источник
Блог*
источник
Блог*
#prog #go #article

How does Go calculate len()..?

Статья о том, как встроенная функция Go len, применимая к пяти семействам типов (слайс, массив, строка, мапа, канал), преобразуется в конкретные операции для каждого типа — от парсинга до кодгена
источник
2022 January 19
Блог*
F::<()> { ptr: &(), __: <_>::default() }

Just normal code written by normal rust programmers
источник
Блог*
Переслано от flexagoon 🇧🇾 Жыве Бе...
посты вафеля be like:


Наконец то в Rust добавили субпространства для реверсивных вызовов xhjqf_pointer::subs!!(<T>)&{[]}, я очень долго ждал этого PR, теперь больше не надо вызывать макрос incl(<<>><T>::__)!
источник
Блог*
А потом ты узнаешь, что это всё вафель и добавил
источник
Блог*
#prog

В кодогенераторе cranelift (который, помимо всего прочего, планируют использовать как бекенд rustc для отладочных билдов) для аллокации регистров используется библиотека regalloc2. В репозитории есть занятный документ — обзор дизайна regalloc2, включающий в себя ожидания от входных данных, высокоуровневую структуру алгоритма, разбор используемых структур данных и различные заметки по поводу производительности (под которые отведён отдельный раздел, но по факту есть и в других местах).

Некоторые интересные цитаты:

* A performance note: merging is extremely performance-sensitive, and it turns out that a mergesort-like merge of the liverange vectors is too expensive, partly because it requires allocating a separate result vector (in-place merge in mergesort is infamously complex). Instead, we simply append one vector onto the end of the other and invoke Rust's builtin sort.

* For each of the preferred and non-preferred register sequences, we probe in an offset manner: we start at some index partway through the sequence, determined by some heuristic number that is random and well-distributed. <...> We then march through the sequence and wrap around, stopping before we hit our starting point again.

The purpose of this offset is to distribute the contention and speed up the allocation process. In the common case where there are enough registers to hold values without spilling (for small functions), we are more likely to choose a free register right away if we throw the dart at random than if we start every probe at register 0, in order. This has a large allocation performance impact in practice.

* We got substantial performance speedups from using vectors rather than linked lists everywhere. This is well-known, but nevertheless, it took some thought to work out how to avoid the need for any splicing, and it turns out that even when our design is slightly less efficient asymptotically (e.g., apend-and-re-sort rather than linear-time merge of two sorted liverange lists when merging bundles), it is faster.

* We use a "chunked sparse bitvec" to store liveness information, which is just a set of VReg indices. The design is fairly simple: the toplevel is a HashMap from "chunk" to a u64, and each u64 represents 64 contiguous indices. <...> We tried a number of other designs as well. Initially we used a simple dense bitvec, but this was prohibitively expensive: O(n^2) space when the real need is closer to O(n) (i.e., a classic sparse matrix). We also tried a hybrid scheme that kept a vector of indices when small and used either a bitvec or a hashset when large. This did not perform as well because (i) it was less memory-efficient (the chunking helps with this) and (ii) insertions are more expensive when they always require a full hashset/hashmap insert.
источник
Блог*
#science #article

Прекрасная интерактивная статья, рассказывающая о том, как работает GPS, с поэтапным учётом всё новых и новых обстоятельств.

(thanks @jemalloc)
источник
Блог*
Кстати, а вы знали, что для нульарных и одноарных кортежей в Rust есть альтернативный синтаксис — с использованием фигурных скобок?

assert!(() == {});
assert!((42) == {42});
источник
2022 January 20
Блог*
#prog #rust #моё

Тем временем в комментариях вспомнили weird-exprs.rs — "Just a grab bag of stuff that you wouldn't want to actually write.". Если быть точнее, то функцию special_characters. Выглядит она так:

fn special_characters() {
   let val = !((|(..):(_,_),__@_|__)((&*"\\",'🤔')/**/,{})=={&[..=..][..];})//
   ;
   assert!(!val);
}

Сегодня я разберу, что же тут происходит — в основном в первой строчке, разумеется. Для начала избавимся от комментариев — они тут нужны только ради того, чтобы ещё больше напугать:

let val = !((|(..):(_,_),__@_|__)((&*"\\",'🤔'),{})=={&[..=..][..];});

Справа отрицание некоего выражения. Вынесем его в отдельную переменную и заодно уберём наружные скобки:

let tmp = (|(..):(_,_),__@_|__)((&*"\\",'🤔'),{})=={&[..=..][..];};
let val = !tmp;

Хм, определение tmp всё ещё выглядит сложноватым... Может, вызвать rustfmt?

let tmp = (|(..): (_, _), __ @ _| __)((&*"\\", '🤔'), {}) == {
   &[..= ..][..];
};

Сильно проще не сделало, но зато теперь видно, что корень дерева выражений — это операция равенства. Посмотрим, что тут справа:

{
   &[..= ..][..];
}

Хм, что тут у нас? У нас тут массив размера 1, в котором лежит выражение ..= .. (первый рендж, кстати, биндится первым и потому выражение в целом имеет тип RangeToInclusive<RangeFull>), который индексируется RangeFull, что работает за счёт deref coercion к соответствующему типу слайса, на полученный слайс берётся ссылка...

Но всё это можно было бы и не рассматривать, поскольку строчка оканчивается точкой с запятой, а потому выражение, каким бы оно ни было, отбрасывается, и в целом получается statement. А блок, который оканчивается statement-ом, как известно, возвращает в Rust ().

Итак, с правой часть равенства разобрались. А что же с левой?

(|(..): (_, _), __ @ _| __)((&*"\\", '🤔'), {})

Здесь у нас после открывающей скобки идёт |. Так как бинарное выражение  с этого оператора начинаться не может, это начала определения замыкания. После заключённого в скобке замыкания идёт её вызов с двумя аргументами. Посмотрим на литерал замыкания поподробнее:

|(..): (_, _), __ @ _| __

Между чёрточками тут два аргумента, разделённых запятой. И тут на полной используется тот факт, что на месте аргументов можно использовать не только идентификаторы, но и произвольные (но которые тайпчекаются, разумеется) паттерны. Вот первый аргумент:

(..): (_, _)

За аргументами в замыканиях может опционально следовать двоеточие и тип аргумента, причём, в отличие от типов в обычных функциях, он не обязательно должен быть выписан полностью. (_, _) тут означает двухместный кортеж с типами, которые нужно вывести компилятору.

(..) же — это паттерн, который матчится с кортежем с произвольным количеством полей. Кстати, с кортежными структурами и кортежными вариантами перечислений этот паттерн тоже работает — достаточно добавить имя. Например, вот этот код тайпчекается:

struct ManyFields(i32, String, char, u8, i32);

fn foo(mf: ManyFields) {
   let ManyFields(..) = mf;
}

Можно даже сматчить только поля из части кортежа:

fn ends(mf: &ManyFields) -> (i32, i32) {
   match mf {
       &ManyFields(start, .., end) => (start, end),
   }
}
источник
Блог*
Впрочем, я отвлёкся.

Второй аргумент — это __ @ _. Как всегда в @-биндингах (ладно, ладно, at-биндингах), справа от @  находится паттерн — в данном случае _, который матчится абсолютно со всем — а слева от @ находится имя, которое привязывается к этому значению, __. Безусловно, идентификатор выглядит странно, но это валидное имя в Rust.

Ещё раз взглянем на замыкание целиком:

|(..): (_, _), __ @ _| __

Тело замыкания состоит из единственного идентификатора __. Иными словами, замыкание просто возвращает свой второй аргумент. С тем же успехом его можно было бы написать так:

|_, second| second

(или, если быть точнее и сохранить тот факт, что первый аргумент является парой, |_: (_, _), second| second).

Окей, с замыканием мы разобрались. Но чтобы понять, что же оно в итоге возвращает, нам нужно посмотреть на то, с какими аргументами оно вызывается:

(замыкание...)((&*"\\", '🤔'), {})

Первый аргумент — это пара (как и ожидается) из &str и char. Фрагмент &*"\\" валиден за счёт того, что строковой литерал имеет тип &'static str и, соответственно, его можно разыменовать (и взять ссылку от результата). Кстати, тут используется синтаксис для escaping-а, поэтому содержимое строки — это единственный символ \. А вот второй аргумент — это пустой блок. В Rust он вычисляется в (). С учётом того, что замыкание просто возвращает свой второй аргумент, получаем, что часть слева от оператора равенства просто возвращает ().

Теперь сложим вместе всё изложенное выше. Переменной val присваивается отрицание от сравнивания () и (). Так как unit всегда равен самому себе, в val записывается значение false. Строчкой ниже в функции вызывается assert!(!val), который ожидаемо проходит.

Как видите, тут нет абсолютно ничего сложного. 👌
источник
Блог*
#prog #rust #itsec

blog.rust-lang.org/2022/01/20/cve-2022-21658.html

> Rust 1.0.0 through Rust 1.58.0 is affected by this vulnerability.

Oof
источник
Блог*
#prog #rust

Вафель двигает Rust вперёд ✊🎉
источник
Блог*
в чате по питону обсуждали такую задачу:
нужно написать функцию add, которая ведёт себя так:

>>> add(1)(2)(3)()
6
>>> add(1)(2)(3)(4)()
10


ну в принципе ничего сложного:

def add(succ = None):
   acc = getattr(add, "_acc", 0)
   if succ is None:
       if hasattr(add, "_acc"):
           del add._acc
       return acc
   add._acc = acc + succ
   return add


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

#![feature(fn_traits, unboxed_closures)]

use std::ops::Add;

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Adder<T> {
   acc: T,
}

impl<L, R, O> FnOnce<(R,)> for Adder<L>
where
   L: Add<R, Output = O>,
{
   type Output = Adder<O>;
   extern "rust-call" fn call_once(self, (succ,): (R,)) -> Self::Output {
       Adder {
           acc: self.acc + succ,
       }
   }
}

impl<A> FnOnce<()> for Adder<A> {
   type Output = A;
   extern "rust-call" fn call_once(self, (): ()) -> Self::Output {
       self.acc
   }
}

#[allow(non_camel_case_types)]
pub struct add;

impl<A> FnOnce<(A,)> for add {
   type Output = Adder<A>;
   extern "rust-call" fn call_once(self, (acc,): (A,)) -> Self::Output {
       Adder { acc }
   }
}

fn main() {
   println!("{}", add(1)(2)(3)());
}


единственное, что не получилось сделать - разрешить делать сразу add()

#prog #rust #python
источник
2022 January 21
Блог*
#prog #meme
источник