Size: a a a

2021 August 08
Блог*
#prog #js #article

- — подходящее имя для пакета на npm, не находите? И да, он существует и скачивается в больших количествах. В статье рассказывается, почему.

(thanks @oleg_log)
источник
2021 August 09
Блог*
#prog #rust #моё

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

В C и C++ есть такая вещь, как zero initialization. Не вдаваясь в подробности, скажу только, что она позволяет инициализировать массив, указав значения не для всех элементов. Остальные значения будут проинициализированы нулём. Что является нулём для каждого отдельного типа — вопрос отдельный, но для числовых типов это будет, собственно, ноль. Можно ли воссоздать данный функционал в Rust? Ну, как вы могли догадаться по факту наличия этого поста — можно!

Но сначала подумаем, как мы можем это организовать. Причём тут есть два вопроса: как заполнять массив и как определять нулевое значение. Решением в духе C++ было бы сделать сначала неинициализированный массив, записать в него данные значения, а потом дописать в него нули (в прямом смысле, через std::mem::zeroed). Такой подход страдает следующими недостатками:
* нужно определить, какие типы можно безопасно инициализировать нулевыми байтами. Можно это сделать через маркерный трейт, но, во-первых, это придётся сделать unsafe трейтом, а во-вторых, это стоило бы сделать auto-трейтом, которые на стабильной версии пока что делать нельзя.
* нужно разбираться с дропом элементов в случае, если создание одного из них запаникует. Не то чтобы это было прям сложно, но это дополнительная возня и дополнительный unsafe.

Но постойте-ка, у нас же есть Rust, мы можем сделать лучше! Заполнять массив будем иначе: сначала размножим нулевое значение, а потом перепишем префикс. Да, у такого подхода есть свои недостатки, и о них обязательно упомяну. Для того, чтобы можно было использовать синтаксис [elem; length], требуется, чтобы elem было либо выражением, имеющим Copy-тип, либо константой. Не будем излишне ограничивать наш подход и будем для нулевого значения использовать ассоциированную константу трейта. Это, в частности, позволит нам использовать "нулевые" значения типов, которые нельзя корректно проинициализировать нулевыми байтами, например, Vec:

trait ConstZero {
   const ZERO: Self;
}

Теперь начнём писать макрос для собственно инициализации (ну да, макрос, а вы как хотели?). Хочется, чтобы то, что мы в итоге сделали, можно было использовать и для инициализации констант. Поэтому ограничимся только операциями, которые можно применять в const-контексте. На вход будем принимать имеющиеся элементы массива и итоговую дину — то, без чего никуда:

macro_rules! arr_init {
   ($($elem:expr,)* ; $len:expr) => {{
       // ...
   }};
}

Если вы внимательно присмотритесь, то увидите, что, в отличие от обычных списков в синтаксисе Rust, тут запятая после каждого элемента обязательно. Я сделал это намеренно, чтобы в случае, если мы переписываем один элемент, синтаксис не совпадал с нативным синтаксисом для массива из повторяющихся значений, а также чтобы подчеркнуть, что пользователь макроса указывает (потенциально) не все значения массива. Перейдём к основной логике макроса:

let mut ret = [<_ as ConstZero>::ZERO; $len];
let mut i = 0usize;
$(
   i += 1;
   ret[i - 1] = $elem;
)*
ret

Использование <_ as ConstZero>::ZERO позволяет подтянуть нулевое значение типа, не называя его. Вот она, сила вывода типов!
источник
Блог*
А вот почему развёрнутый цикл написан столь странным образом? Казалось бы, ничто не мешает написать так:

$(
   ret[i] = $elem;
   i += 1;
)*

Мешает компилятор. В сгенерированном коде останется инкремент индекса после инициализации префикса. Компилятор будет справедливо ругаться, что значение, записанное в индекс, нигде не используется. Это предупреждение можно подавить, но выглядеть это будет несколько уродливо:

$(
   ret[i] = $elem;
   #[allow(unused_assignments)]
   {
       i += 1;
   }
)*

Разместить #[allow(unused_assignments)] непосредственно на присваивании нельзя — это будет считаться атрибутом на выражении, что компилятор Rust на стабильной версии не поддерживается. Более того, это было бы не совсем верно в том смысле, что только последняя из операций реально нуждается в подавлении предупреждений. Это можно было бы решить более аккуратным матчингом с явным выделением первого или последнего значения, но, на мой взгляд, это неоправданное усложнение.

Внимательный читатель мог бы также заметить, что в случае, если список, захватываемый фрагментом $($elem:expr,)*, пустой, то компилятор будет жаловаться на мертвый код из-за объявления неиспользуемой переменной i. Но оно и к лучшему: это значит, что в месте, где макрос используется, его вызов выглядит, как array_init![; length], что, на мой взгляд, лучше переписать в виде [TyName::ZERO; length], и предупреждение компилятора будет подталкивать к этому варианту.

Закончили ли мы с ядром логики? Не совсем: что будет, если пользователь перечислит больше элементов, чем длина массива? Ну, будет паника. В рантайме, ага. Но погодите, у нас же вся информация есть на этапе компиляции! Значит, имеет смысл на этапе компиляции же эту ошибку и предотвратить. Для этого мы будем использовать уже ставший традиционным приёмом: сделаем константу, выражение для которой при ошибке не будет тайпчекаться и потому будет стопорить компиляцию. Более конкретно, мы сделаем массив из () той же длины, что и результирующий массив, а попытаемся приписать значение массива из () длины, являющейся максимумом от длины результирующего массива и количества переданных элементов. Если элементов слишком много, то типы слева и справа не совпадут:

const LEN: usize = $len;
const _INIT_LEN: usize = $({stringify!($elem); 1usize} +)* 0;
const _ASSERT_NOT_TOO_MANY: [(); LEN] = [(); if LEN >= _INIT_LEN { LEN } else { _INIT_LEN }];

А вот теперь можно приступить к той части, где мы пытаемся мимикрировать под синтаксис C++ в том плане, что указываем только часть элементов массива. Для этого нам надо распарсить: (опционально) ключевое слово mut, имя переменной, двоеточие, тип массива (то, откуда мы возьмём длину массива), знак равно и литерал массива. Элементы у нас имеются, длина — тоже, а значит, ничто не мешает вызвать array_init:

macro_rules! arr_let {
   ($name:ident: [$ty:ty; $len:expr] = [$($elem:expr,)*]) => {
       let $name: [$ty; $len] = arr_init![$($elem,)*; $len];
   };
   // аналогично для mut
}

Здесь запятые после элементов также обязательны по указанной выше причине.

Полностью аналогично пишется определение для инициализации констант. Надо только иметь в виду, что const-блоков пока не завезли, так что придётся помещать код внутрь const-функции:

macro_rules! arr_const {
   ($name:ident: [$ty:ty; $len:expr] = [$($elem:expr,)*]) => {
       const $name: [$ty; $len] = {
           const fn init_arr() -> [$ty; $len] {
               arr_init![$($elem,)*; $len]
           }
           init_arr()
       };
   };
}
источник
Блог*
Написав немного реализаций ConstZero для общеупотребимых типов (с вашего позволения, я это опущу, ибо код тривиальный), мы можем написать, скажем, так:

#[derive(Debug)]
struct Thirteen(u32);

impl ConstZero for Thirteen {
   const ZERO: Self = Self(13);
}

arr_const!(ARRAY: [Thirteen; 13] = [Thirteen(15), Thirteen(14),]);

fn main() {
   println!("{:?}", ARRAY);

   arr_let!(arr: [_; 3] = [([vec![2u64, 3]], (("hey",), (2i32, 3.14f64, ()), [true, true])),]);
   let all_but_one_zeros = arr_init![128u128,; 7];

   println!("{:?}", arr);
   println!("{:?}", all_but_one_zeros);

   // не компилируется
   // let wrong = arr_init![(),; 0];
}

Вполне работает!

Теперь поговорим о недостатках:
* самый существенный: ввиду избранного способа мы не можем инициализировать значениями типов с нетривиальным дропом (скажем, Vec), даже если их значения можно сконструировать на этапе компиляции.
* пользователю приходится так или иначе сообщать о длине массива, причём даже тип массива в arr_let! нельзя заменить на псевдоним. Честное слово, я пытался обойти это недостаток многими способами — каждый раз я утыкался цикл в выводе типов, который не давал скомпилировать код.
* если переписанных значений достаточно много, то сгенерированный код тратит время в рантайме на запись "нулевых" значений, которые потом будут перезаписаны. Да, компилятор может убрать dead store, но не в том случае, если выражения в макросе потенциально могут паниковать.
* ну и макросы написаны не так, чтобы можно было сразу использовать их в библиотеке (не хватает #[macro_export], $crate и поддержки атрибутов), но это легко поправимо.

На этом всё. Как всегда, весь код  в гисте.
источник
Блог*
Кстати, не знаю, когда это появилось, но на Rust playground теперь в числе инструментов есть раскрытие макросов (Tools > Expand macros). Может пригодиться для подобных вещей
источник
Блог*
#prog #rust #python #amazingopensource
источник
Блог*
R&D все-таки штука неблагодарная. Выбегаешь такой к коллегам - пацаны, работает! Rust запускает питоновские concurrent.futures параллельно в GIL-треде и получает await'ом результат через tokio!

А они тебя поздравляют, из вежливости. И догадываются, что раз tokio, значит это что-то с Японией.
источник
Блог*
Собственно насчёт сумрачной технологии интегрирования питона в раст - получилось оформить в crate. Производительность "всунуть задачу в ThreadPoolExecutor и подождать" - "терпимая", но стабильность - отличная. Главное, что задачи ставятся из любого Rust-потока, в котором работает coro, GIL не мешает.

https://crates.io/crates/pime
источник
2021 August 10
Блог*
#meme
источник
2021 August 11
Блог*
#prog #article

Статья о задачах на пути к созданию GUI-фреймворка (спойлер: их дофига)
источник
Блог*
#prog #rust

Наглядный пример, объясняющий вариантность в контексте Rust (см. также соответствующую главу Растономикона).

(thanks @wafflelapkin)
источник
2021 August 12
Блог*
xxx: можно же было в стд сделать одеяло-имплементацию

#quotes #трудовыебудни #justrusteceanthings
источник
2021 August 13
Блог*
log(😅) = 💧log(😄)
источник
Блог*
источник
Блог*
источник
Блог*
источник
2021 August 14
Блог*
Согласно мнению одного человека, я — упырь 👌😐
источник
Блог*
Посуда бьётся к счастью. А сердечки?
источник
Блог*
#prog #rust

Как вам, наверное, известно, сырые указатели *mut T и *const T не реализуют Send и Sync. Кто-то может сказать: "Но это же просто адреса, к чему эти дурацкие ограничения? К тому же, они тривиально обходятся обёртками с ручными unsafe impl Send/Sync". На это есть две причины.

Первая: Send и Sync — это авто-трейты: если все поля типа реализуют Send (Sync) и у него нету явного impl-а Send (Sync), то и сам тип реализует Send (Sync). Крайне редко сырые указателя используются как поля, которые просто передаются дальше — почти всегда из используют тем или иным способом — и при том не всегда потокобезопасным. Если бы сырые указатели реализовывали бы эти трейты, то слишком много типов автоматически считались бы Send (Sync) не смотря на то, что они таковыми не являются. Например, Rc<T> выглядит примерно так:

struct Rc<T> {
   ptr: *const RcBox<T>
}


struct RcBox<T> {
   strong: usize,
   weak: usize,
   val: T,
}



С "упрощающим" изменением Rc<T> всегда бы реализовывал бы Send и Sync, что, очевидно, неправильно — в действительности же Rc<T> не реализует ни один из этих трейтов, поскольку использует не атомарный счётчик ссылок.

Вторая: явное выделение ответственности. Если вам требуется переслать сырой указатель из одного потока в другой или расшарить его с несколькими потоками, то у вас наверняка есть некоторые соображения, почему это можно сделать. Эти соображения, очевидно, работают не для всех указателей. Обычно мы разделяем данные с одним и тем же представлением, но разной семантикой в разные типы. Так почему мы не должны делать то же самое для указателей? Мало того, что это позволяет переместить утверждения в справедливости из невидимых компилятору комментариев в проверяемые компилятором типы, так оно ещё и помогает людям видеть, где необходимые предусловия и инварианты и впрямь соблюдены. А выделение специальных конструкторов над обёртками сырых указателей позволяет на практике реализовать принцип parse, don't validate.
источник
2021 August 15
Блог*
Дети по-античному просты.

Я стоял в очереди в зоомагазине, а передо мной семилетний (скажем) мальчик что-то объяснял матери. Ребенку приходилось кричать, так как все звери в этом зоомагазине были полны энергии. Мама, отвлеченная щебетом попугаев, что-то переспросила, и ребенок разгневался.

— Птицы, молчите! — вскрикнул он голосом персидского царя.

Я почти ожидал, что все действительно смолкнет, но, конечно же, птицы не послушались. На лице мальчика на секунду застыла гримаса брезгливой ярости.

На выходе из магазина он наступил в лужу и, клянусь, почти приказал матери ее высечь.
источник