Forwarded from Санечка Ъысь (Anna Weiss)
This media is not supported in your browser
VIEW IN TELEGRAM
#prog #rust #моё
Одна из частых ошибок новичков в Rust — это ошибка при считывание строк из файла. Они вызывают в цикле
Не будем решать сугубо конкретную задачу очищения строки и решим сразу задачу выполнения произвольного кода при окончании итерации цикла (на самом деле просто при дропе, но нас сейчас интересует именно цикл). Очевидно, для того, чтобы добиться нужного нам эффекта, требуются ссылка на значение и действие, которое будет на нём вызвано. Для простоты рассмотрим вариант, когда действие одно и тоже для каждой операции дропа — в этом случае можно сохранить значение и это действие в одной структуре и, соответственно, хранить в структуре с интересующим нас дропом одну лишь ссылку на этот агрегат:
Теперь выпишем определение интересующей нас структуры — точнее, попытаемся:
Нам нужно будет описать поведение этой структуры при дропе, но для этого требуется, чтобы ограничение на тип
Помним, что на самом деле это сахар для higher-ranked trait bounds:
Так что же в дропе? Просто вызываем действие на значении:
Скобки вокруг
Для удобства использования — ну и для того, чтобы не нарушать инкапсуляцию — реализуем для
Теперь покажем, как это работает. Сделаем простую программу: она будет считывать ввод по строкам и печатать их с переводом в верхний регистр, кроме строк, содержащих
Вот и всё! Как всегда, весь код в гисте.
Одна из частых ошибок новичков в Rust — это ошибка при считывание строк из файла. Они вызывают в цикле
read_to_string
, но забывают очищать строку в конце итерации, из-за чего в строке остаётся то, что было прочитано на предыдущих операциях. Более того, просто воткнуть s.clear();
в конце тела может быть недостаточно: если в цикле есть оператор continue
, то до исполнения этой строчки дело может и не дойти — в подобных случаях не только новички делают ошибки. Было бы хорошо иметь что-то, что выполняется автоматически в конце каждой итерации цикла вне зависимости то того, как она выполняется... И такая вещь в Rust есть: Drop::drop
aka деструктор!Не будем решать сугубо конкретную задачу очищения строки и решим сразу задачу выполнения произвольного кода при окончании итерации цикла (на самом деле просто при дропе, но нас сейчас интересует именно цикл). Очевидно, для того, чтобы добиться нужного нам эффекта, требуются ссылка на значение и действие, которое будет на нём вызвано. Для простоты рассмотрим вариант, когда действие одно и тоже для каждой операции дропа — в этом случае можно сохранить значение и это действие в одной структуре и, соответственно, хранить в структуре с интересующим нас дропом одну лишь ссылку на этот агрегат:
struct AutoDo<T, F> {
value: T,
action: F,
}
Теперь выпишем определение интересующей нас структуры — точнее, попытаемся:
struct DoOnDrop<'a, T, F>(&'a mut AutoDo<T, F>);
Нам нужно будет описать поведение этой структуры при дропе, но для этого требуется, чтобы ограничение на тип
F
— которое неизбежно появится для описания того факта, что это функция — повторялось при определении структуры. Без выполнения этого требования компилятор выдаст ошибку. А как, собственно говоря, должно выглядеть это требование? Это должна быть функция, которая принимает мутабельную ссылку на значение, причём не с каким-то конкретным временем жизни, а с произвольным. В итоге определение выглядит так:struct DoOnDrop<'a, T, F: FnMut(&mut T)>(&'a mut AutoDo<T, F>);
Помним, что на самом деле это сахар для higher-ranked trait bounds:
struct DoOnDrop<'a, T, F: for<'val> FnMut(&'val mut T)>(&'a mut AutoDo<T, F>);
Так что же в дропе? Просто вызываем действие на значении:
impl<'a, T, F: FnMut(&mut T)> Drop for DoOnDrop<'a, T, F> {
fn drop(&mut self) {
(self.0.action)(&mut self.0.value)
}
}
Скобки вокруг
self.0.action
тут необходимы, поскольку иначе это будет проинтепретировано, как вызов метода AutoDo::action
, коего, разумеется нет.Для удобства использования — ну и для того, чтобы не нарушать инкапсуляцию — реализуем для
DoOnDrop
трейты Deref{, Mut}<Target = T>
, а также метод AutoDo::get
, который будет оборачивать ссылку в DoOnDrop
. Реализацию, с вашего позволения, опущу в силу тривиальности.Теперь покажем, как это работает. Сделаем простую программу: она будет считывать ввод по строкам и печатать их с переводом в верхний регистр, кроме строк, содержащих
nope
— их она будет просто отбрасывать:fn main() {
use std::io::Read;
let stdin = std::io::stdin();
let mut stdin = stdin.lock();
let mut buf = AutoDo::new(String::new(), String::clear);
loop {
let mut buf = buf.get();
if let Ok(0) | Err(_) = stdin.read_to_string(&mut buf) {
break;
}
if buf.contains("nope") {
continue; // работает и с continue
}
println!("{}", buf.to_uppercase());
}
let buf = buf.into_inner();
assert!(buf.is_empty()); // буфер в итоге пустой
}
Вот и всё! Как всегда, весь код в гисте.
Telegram
Блог*
Прошу прощения. В общем, я хотел рассказать про одну продвинутую фичу системы типов Rust...
Так, мне уже это не нравится
...Которая формально не связана с замыканиями, но фактически была введена ради них. Я говорю про higher-ranked trait bounds. В качестве…
Так, мне уже это не нравится
...Которая формально не связана с замыканиями, но фактически была введена ради них. Я говорю про higher-ranked trait bounds. В качестве…
#prog #rust
Интересный способ обойти (для некоторых случаев) нехватку trait bounds в const fn при помощи GAT.
twitter.com/DrawsMiguel/status/1422770131969716228
Интересный способ обойти (для некоторых случаев) нехватку trait bounds в const fn при помощи GAT.
twitter.com/DrawsMiguel/status/1422770131969716228
Twitter
mcyoung
trait-bounds-in-const? who needs that when you have gats? godbolt.org/z/Yvb1vMdvz
#prog #article
Занятная статья об оптимизации базы данных. Один из тех редких случаев, когда промахи кеша инструкций действительно стали сдерживающим фактором. Что удивительно, эту проблему удалось решить довольно небольшими изменениями, которые в итоге привели к снижению задержки (в смысле latency), причём как средней, так и хвостовых перцентилей.
Занятная статья об оптимизации базы данных. Один из тех редких случаев, когда промахи кеша инструкций действительно стали сдерживающим фактором. Что удивительно, эту проблему удалось решить довольно небольшими изменениями, которые в итоге привели к снижению задержки (в смысле latency), причём как средней, так и хвостовых перцентилей.
ScyllaDB
ScyllaDB's Approach to Improve Performance for CPU-bound workloads - ScyllaDB
A database like ScyllaDB can be limited by the network, disk I/O or the processor. Which one it is often dynamic and depends on both the hardware configuration and the workload. The only way of dealing with that is to attempt to achieve good throughput and…
Как называется ситуация, когда посылка из Москвы в Санкт-Петербург ездит по Транссибирской магистрали?
Логистическая регрессия.
Логистическая регрессия.
Forwarded from const_cast
Зачем уходить из C++?
Самый важный вопрос: зачем вообще что-то менять?
С годами понял такую вещь: если у тебя есть перфекционистские наклонности, то изучать C++ тебе противопоказано. Этот язык манит ощущением полного контроля над происходящим, но на деле все эти обещания, словно узоры на крыльях бабочки, рассыпаются в прах, стоит лишь к ним прикоснуться. В итоге твоя карьера программиста на C++ вырождается в попытки заставить эту бабочку двигаться по твоему маршруту, не касаясь её крыльев, иначе она упадёт и больше не сможет летать.
Например, ты никогда не можешь быть уверен, что твоя программа на C++ не содержит неопределённого поведения, разрешающего оптимизирующему компилятору просто удалить к чертям содержимое какой-нибудь функции полностью. В одной версии всё работает, а в новой — раз, и всё поломалось. Такое случается сплошь и рядом.
Дичайшая разрозненность экосистемы — ещё одна из проблем, разъедающих C++ изнутри. Нет единого способа сделать вообще хоть что-то. У абсолютно каждого «эксперта» в C++ своё мнение о том, как решать те или иные задачи.
Я перестал пытаться делать пет-проджекты на C++, потому что вместо того, чтобы реализовывать свою идею, я всё своё время тратил на подбор подходящих библиотек: эта слишком жирная, тут система сборки непонятная, эту поддерживает один калека, эта не кроссплатформенная, и т.д., и т.п.
Слишком много свободы и слишком мало контроля — всё это вырождается в то, что есть тысячи недоделанных решений, ни одно из которых не подходит тебе полностью — потому что, ну, можно ведь лучше вот тут сделать! И оптимизированнее! И вот ты уже в одном шаге от переизобретения велосипеда. Вместо того, чтобы пилить по кайфу свой пет-проджект.
И язык развивается куда-то не туда. С каждой новой версией стандарта C++ становится всё больше недовольных тем, что фундаментальные проблемы языка не решаются вообще никак. Вместо этого мы просто получаем очередную кучу фич, предназначенных для всех сразу и ни для кого по отдельности. Взять хотя бы многострадальный
В результате, в каждой команде для поддержания хоть какого-то порядка будет разрешено только своё собственное подмножество языка, за пределы которого выходить ни в коем случае нельзя, иначе всё вмиг развалится.
И вот реально все по-своему пишут. Единства нет и не предвидится.
Если честно, мне всё это очень надоело. С моим устройством мышления очень сложно оставаться продуктивным в таком болоте: слишком много времени и мозга уходит на технические детали и решение «детских» проблем языка, а не на то, чтобы заставить компьютер делать то, что ты от него хочешь. Мотивация в этом ковыряться уже давно иссякла. Я в программирование не за этим пришёл.
Самый важный вопрос: зачем вообще что-то менять?
С годами понял такую вещь: если у тебя есть перфекционистские наклонности, то изучать C++ тебе противопоказано. Этот язык манит ощущением полного контроля над происходящим, но на деле все эти обещания, словно узоры на крыльях бабочки, рассыпаются в прах, стоит лишь к ним прикоснуться. В итоге твоя карьера программиста на C++ вырождается в попытки заставить эту бабочку двигаться по твоему маршруту, не касаясь её крыльев, иначе она упадёт и больше не сможет летать.
Например, ты никогда не можешь быть уверен, что твоя программа на C++ не содержит неопределённого поведения, разрешающего оптимизирующему компилятору просто удалить к чертям содержимое какой-нибудь функции полностью. В одной версии всё работает, а в новой — раз, и всё поломалось. Такое случается сплошь и рядом.
Дичайшая разрозненность экосистемы — ещё одна из проблем, разъедающих C++ изнутри. Нет единого способа сделать вообще хоть что-то. У абсолютно каждого «эксперта» в C++ своё мнение о том, как решать те или иные задачи.
Я перестал пытаться делать пет-проджекты на C++, потому что вместо того, чтобы реализовывать свою идею, я всё своё время тратил на подбор подходящих библиотек: эта слишком жирная, тут система сборки непонятная, эту поддерживает один калека, эта не кроссплатформенная, и т.д., и т.п.
Слишком много свободы и слишком мало контроля — всё это вырождается в то, что есть тысячи недоделанных решений, ни одно из которых не подходит тебе полностью — потому что, ну, можно ведь лучше вот тут сделать! И оптимизированнее! И вот ты уже в одном шаге от переизобретения велосипеда. Вместо того, чтобы пилить по кайфу свой пет-проджект.
И язык развивается куда-то не туда. С каждой новой версией стандарта C++ становится всё больше недовольных тем, что фундаментальные проблемы языка не решаются вообще никак. Вместо этого мы просто получаем очередную кучу фич, предназначенных для всех сразу и ни для кого по отдельности. Взять хотя бы многострадальный
std::variant
— могли бы сделать нормальный паттерн-матчинг, а получилось какое-то дикое говно, которое невозможно читать, а написать корректно ещё сложнее. И ещё пачку новых способов инициализации, куда же без этого.В результате, в каждой команде для поддержания хоть какого-то порядка будет разрешено только своё собственное подмножество языка, за пределы которого выходить ни в коем случае нельзя, иначе всё вмиг развалится.
И вот реально все по-своему пишут. Единства нет и не предвидится.
Если честно, мне всё это очень надоело. С моим устройством мышления очень сложно оставаться продуктивным в таком болоте: слишком много времени и мозга уходит на технические детали и решение «детских» проблем языка, а не на то, чтобы заставить компьютер делать то, что ты от него хочешь. Мотивация в этом ковыряться уже давно иссякла. Я в программирование не за этим пришёл.
#prog #js #article
(thanks @oleg_log)
-
— подходящее имя для пакета на npm, не находите? И да, он существует и скачивается в больших количествах. В статье рассказывается, почему.(thanks @oleg_log)
BleepingComputer
Empty npm package '-' has over 700,000 downloads — here's why
A mysterious, one-letter npm package named "-" sitting on the registry since 2020 has received over 700,000 downloads. What's more? The package contains no functional code, so what makes it score so many downloads?
#prog #rust #моё
Как вы знаете, в Rust массив можно инициализировать при помощи литералов двумя способами. Первый — перечислить все элементы массива через запятую внутри квадратных скобок. Второй — в квадратных скобках указать значение и после точки с запятой — длину массива: в этом случае массив будет заполнен копиями указанного значения. Так или иначе, нужно задать всё элементы массива сразу. И это не всегда бывает удобно.
В C и C++ есть такая вещь, как zero initialization. Не вдаваясь в подробности, скажу только, что она позволяет инициализировать массив, указав значения не для всех элементов. Остальные значения будут проинициализированы нулём. Что является нулём для каждого отдельного типа — вопрос отдельный, но для числовых типов это будет, собственно, ноль. Можно ли воссоздать данный функционал в Rust? Ну, как вы могли догадаться по факту наличия этого поста — можно!
Но сначала подумаем, как мы можем это организовать. Причём тут есть два вопроса: как заполнять массив и как определять нулевое значение. Решением в духе C++ было бы сделать сначала неинициализированный массив, записать в него данные значения, а потом дописать в него нули (в прямом смысле, через
* нужно определить, какие типы можно безопасно инициализировать нулевыми байтами. Можно это сделать через маркерный трейт, но, во-первых, это придётся сделать unsafe трейтом, а во-вторых, это стоило бы сделать auto-трейтом, которые на стабильной версии пока что делать нельзя.
* нужно разбираться с дропом элементов в случае, если создание одного из них запаникует. Не то чтобы это было прям сложно, но это дополнительная возня и дополнительный unsafe.
Но постойте-ка, у нас же есть Rust, мы можем сделать лучше! Заполнять массив будем иначе: сначала размножим нулевое значение, а потом перепишем префикс. Да, у такого подхода есть свои недостатки, и о них обязательно упомяну. Для того, чтобы можно было использовать синтаксис
Как вы знаете, в Rust массив можно инициализировать при помощи литералов двумя способами. Первый — перечислить все элементы массива через запятую внутри квадратных скобок. Второй — в квадратных скобках указать значение и после точки с запятой — длину массива: в этом случае массив будет заполнен копиями указанного значения. Так или иначе, нужно задать всё элементы массива сразу. И это не всегда бывает удобно.
В C и C++ есть такая вещь, как zero initialization. Не вдаваясь в подробности, скажу только, что она позволяет инициализировать массив, указав значения не для всех элементов. Остальные значения будут проинициализированы нулём. Что является нулём для каждого отдельного типа — вопрос отдельный, но для числовых типов это будет, собственно, ноль. Можно ли воссоздать данный функционал в Rust? Ну, как вы могли догадаться по факту наличия этого поста — можно!
Но сначала подумаем, как мы можем это организовать. Причём тут есть два вопроса: как заполнять массив и как определять нулевое значение. Решением в духе C++ было бы сделать сначала неинициализированный массив, записать в него данные значения, а потом дописать в него нули (в прямом смысле, через
std::mem::zeroed
). Такой подход страдает следующими недостатками:* нужно определить, какие типы можно безопасно инициализировать нулевыми байтами. Можно это сделать через маркерный трейт, но, во-первых, это придётся сделать unsafe трейтом, а во-вторых, это стоило бы сделать auto-трейтом, которые на стабильной версии пока что делать нельзя.
* нужно разбираться с дропом элементов в случае, если создание одного из них запаникует. Не то чтобы это было прям сложно, но это дополнительная возня и дополнительный unsafe.
Но постойте-ка, у нас же есть Rust, мы можем сделать лучше! Заполнять массив будем иначе: сначала размножим нулевое значение, а потом перепишем префикс. Да, у такого подхода есть свои недостатки, и о них обязательно упомяну. Для того, чтобы можно было использовать синтаксис
[elem; length]
, требуется, чтобы elem
было либо выражением, имеющим Copy
-тип, либо константой. Не будем излишне ограничивать наш подход и будем для нулевого значения использовать ассоциированную константу трейта. Это, в частности, позволит нам использовать "нулевые" значения типов, которые нельзя корректно проинициализировать нулевыми байтами, например, Vec
:trait ConstZero {Теперь начнём писать макрос для собственно инициализации (ну да, макрос, а вы как хотели?). Хочется, чтобы то, что мы в итоге сделали, можно было использовать и для инициализации констант. Поэтому ограничимся только операциями, которые можно применять в const-контексте. На вход будем принимать имеющиеся элементы массива и итоговую дину — то, без чего никуда:
const ZERO: Self;
}
macro_rules! arr_init {Если вы внимательно присмотритесь, то увидите, что, в отличие от обычных списков в синтаксисе Rust, тут запятая после каждого элемента обязательно. Я сделал это намеренно, чтобы в случае, если мы переписываем один элемент, синтаксис не совпадал с нативным синтаксисом для массива из повторяющихся значений, а также чтобы подчеркнуть, что пользователь макроса указывает (потенциально) не все значения массива. Перейдём к основной логике макроса:
($($elem:expr,)* ; $len:expr) => {{
// ...
}};
}
let mut ret = [<_ as ConstZero>::ZERO; $len];Использование
let mut i = 0usize;
$(
i += 1;
ret[i - 1] = $elem;
)*
ret
<_ as ConstZero>::ZERO
позволяет подтянуть нулевое значение типа, не называя его. Вот она, сила вывода типов!А вот почему развёрнутый цикл написан столь странным образом? Казалось бы, ничто не мешает написать так:
Внимательный читатель мог бы также заметить, что в случае, если список, захватываемый фрагментом
Закончили ли мы с ядром логики? Не совсем: что будет, если пользователь перечислит больше элементов, чем длина массива? Ну, будет паника. В рантайме, ага. Но погодите, у нас же вся информация есть на этапе компиляции! Значит, имеет смысл на этапе компиляции же эту ошибку и предотвратить. Для этого мы будем использовать уже ставший традиционным приёмом: сделаем константу, выражение для которой при ошибке не будет тайпчекаться и потому будет стопорить компиляцию. Более конкретно, мы сделаем массив из
Полностью аналогично пишется определение для инициализации констант. Надо только иметь в виду, что const-блоков пока не завезли, так что придётся помещать код внутрь const-функции:
$(Мешает компилятор. В сгенерированном коде останется инкремент индекса после инициализации префикса. Компилятор будет справедливо ругаться, что значение, записанное в индекс, нигде не используется. Это предупреждение можно подавить, но выглядеть это будет несколько уродливо:
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;А вот теперь можно приступить к той части, где мы пытаемся мимикрировать под синтаксис C++ в том плане, что указываем только часть элементов массива. Для этого нам надо распарсить: (опционально) ключевое слово
const _INIT_LEN: usize = $({stringify!($elem); 1usize} +)* 0;
const _ASSERT_NOT_TOO_MANY: [(); LEN] = [(); if LEN >= _INIT_LEN { LEN } else { _INIT_LEN }];
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()
};
};
}
Написав немного реализаций
Теперь поговорим о недостатках:
* самый существенный: ввиду избранного способа мы не можем инициализировать значениями типов с нетривиальным дропом (скажем, Vec), даже если их значения можно сконструировать на этапе компиляции.
* пользователю приходится так или иначе сообщать о длине массива, причём даже тип массива в
* если переписанных значений достаточно много, то сгенерированный код тратит время в рантайме на запись "нулевых" значений, которые потом будут перезаписаны. Да, компилятор может убрать dead store, но не в том случае, если выражения в макросе потенциально могут паниковать.
* ну и макросы написаны не так, чтобы можно было сразу использовать их в библиотеке (не хватает
На этом всё. Как всегда, весь код в гисте.
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
и поддержки атрибутов), но это легко поправимо.На этом всё. Как всегда, весь код в гисте.
Gist
Инициализация массивов a la C++
Инициализация массивов a la C++. GitHub Gist: instantly share code, notes, and snippets.
Блог*
Написав немного реализаций ConstZero для общеупотребимых типов (с вашего позволения, я это опущу, ибо код тривиальный), мы можем написать, скажем, так: #[derive(Debug)] struct Thirteen(u32); impl ConstZero for Thirteen { const ZERO: Self = Self(13);…
Кстати, не знаю, когда это появилось, но на Rust playground теперь в числе инструментов есть раскрытие макросов (Tools > Expand macros). Может пригодиться для подобных вещей
Forwarded from Segment@tion fault
R&D все-таки штука неблагодарная. Выбегаешь такой к коллегам - пацаны, работает! Rust запускает питоновские concurrent.futures параллельно в GIL-треде и получает await'ом результат через tokio!
А они тебя поздравляют, из вежливости. И догадываются, что раз tokio, значит это что-то с Японией.
А они тебя поздравляют, из вежливости. И догадываются, что раз tokio, значит это что-то с Японией.
Forwarded from Segment@tion fault
Собственно насчёт сумрачной технологии интегрирования питона в раст - получилось оформить в crate. Производительность "всунуть задачу в ThreadPoolExecutor и подождать" - "терпимая", но стабильность - отличная. Главное, что задачи ставятся из любого Rust-потока, в котором работает coro, GIL не мешает.
https://crates.io/crates/pime
https://crates.io/crates/pime