1.83K subscribers
3.3K photos
132 videos
15 files
3.58K links
Блог со звёздочкой.

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

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
Блог*
2. Если нам нужно иметь таблицу функций для каждого значения некоего типа, наиболее прямолинейный способ достичь этого — это включить эту таблицу в каждое значение. Так поступают компиляторы всех мейнстримных объектно-ориентированных языков программирования…
3. А что, если мы не будем в лоб включать vtable в каждое значение, а передавать указатель на неё рядом со значением? Так происходит в Rust при использовании dyn Trait и в Haskell при использовании экзистенциальной типизации. Компромиссы при этом схожи с предыдущим решением, поэтому я сконцентрируюсь на отличиях.

Достоинства:
* Использование функций в vtable требует одного косвенного доступа вместо двух.
* Апкаст к разным интерфейсам требует лишь замены указателя на vtable, сам аргумент трогать не надо. Для каждой конкретной комбинации интерфейсов можно составить свою vtable, поэтому тут нет проблем с vtable неизвестного размера.
* Так как само значение не включает в себя vtable, компилятор в состоянии в тех случаях, когда конкретный нижележащий тип известен, может передавать его по значению и, таким образом, избежать выделения памяти в куче, даже если значение уходит из функции, в которой было порождено. Также в этом случае компилятор может заинлайнить функции из vtable.

Недостатки:
* Фактически, в этом случае мы передаём по два указателя на аргумент, что в некоторых случаях может оказаться расточительно (кеш процессора всё же не очень большой).
* Вместо того, чтобы иметь по одной vtable в бинарнике на реализацию интерфейса типом, при использовании статической схемы распределения vtable для апкастов мы вынуждены хранить в бинарнике каждую комбинацию интерфейсов для каждого типа, которая используется в программе. В этом случае мы занимаем место в бинарнике дублирующимся определениями. В принципе, это можно решить, передавая отдельно указатели на каждую vtable, но лично мне неизвестен ни один язык, в котором это реально использовалось бы (если вы вдруг знаете — напишите в личку или в чат @decltype_chat_ptr_t). Подобное предлагалось к реализации в Rust, но дальше обсуждений дело так и не зашло (нет, ссылку на RFC я сейчас найти не могу).
Блог*
3. А что, если мы не будем в лоб включать vtable в каждое значение, а передавать указатель на неё рядом со значением? Так происходит в Rust при использовании dyn Trait и в Haskell при использовании экзистенциальной типизации. Компромиссы при этом схожи с предыдущим…
4. Максимальной гибкости мы достигаем, передавая vtable отдельно для каждого типа аргумента вместо того, чтобы привызывать к отдельным аргументам. В этом случае vtable могут передаваться в рантайме (Scala), на этапе компиляции (Rust) или тогда, когда решит компилятор (Haskell, но там, как правило, передаётся в итоге на этапе компиляции).

Достоинства:
* Мы можем спокойно мономорфизировать обобщённые функции и инлайнить функции из vtable.
* Тот факт, что vtable одинаковая для обоих аргументов, позволяет использовать описывать обобщённые функции вида fn add<T>(a T, b T) -> T
* Тот факт, что мы можем передавать vtable, не передавая самого аргумента этого типа, позволяет писать обобщённые функции вида fn default<T>() ->T (и, что немаловажно, строить на их базе новые, вроде fn default_pair() -> (T, T)).

Недостатки:
* Мономорфизованные функции увеличивают итоговый размер бинарника.
* Динамическая линковка сильно затруднена (хотя и возможна).
* vtable нужно как-то передавать в обобщённые функции. Передавать их явно слишком неудобно, поэтому в тех языках, где подобные механизмы есть (Rust, Haskell, Scala), нужно каким-то образом определять, какую именно реализацию интерфейса для данного типа надо передавать. Это — отдельная сложная проблема, и то, как её решают — тема, достойная отдельного поста.
Чёрт возьми, как же в телеге неудобно лонгриды писать
Каждый раз, когда ты принимаешь Vec<T> по ссылке, где-то в мире ругается один Клиппи
#prog #rust #article #rustlib

О том, как писать быстрый код на Rust, вкупе с советами по профилированию кода. Рассказывается на примере библиотеки fasteval, по настоящему быстрой библиотеки для подсчёта арифметических выражений.

likebike.com/posts/How_To_Write_Fast_Rust_Code.html
#prog #rust #article

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

adventures.michaelfbryan.com/posts/pragmatic-global-state/
#prog #rust #rustlib

Кому-то в расточате требовались параметризованные тесты. Так вот, такая библиотека есть.

crates.io/crates/test-case
#prog #rust #rustlib #amazingopensource

Библиотека, которая возвращает Cow вместо String для трансформированных строк, если это возможно. Всегда знал, что кто-то напишет нечто подобное, на мой взгляд, очень полезная вещь.

https://github.com/RReverser/cow-utils-rs
Блог*
#prog #rust #article Продолжая тему строк в Rust: развёрнутая статья, которая достаточно доходчиво объясняет, почему в Rust два типа строк. Да и написано так, что читать легко и приятно. https://fasterthanli.me/blog/2020/working-with-strings-in-rust/
#prog #rust #amazingopensource

Ещё одна библиотека от небезызвестного Кладова (ссылка взята из статьи выше). Собственно, описание достаточно красноречиво:

A SmolStr is a string type that has the following properties:

* size_of::<SmolStr>() == size_of::<String>()
* Clone is O(1)
* Strings are stack-allocated if they are:
* Up to 22 bytes long
* Longer than 22 bytes, but substrings of WS (see src/lib.rs). Such strings consist solely of consecutive newlines, followed by consecutive spaces
* If a string does not satisfy the aforementioned conditions, it is heap-allocated

Unlike String, however, SmolStr is immutable. The primary use case for SmolStr is a good enough default storage for tokens of typical programming languages. Strings consisting of a series of newlines, followed by a series of whitespace are a typical pattern in computer programs because of indentation. Note that a specialized interner might be a better solution for some use cases.

https://github.com/rust-analyzer/smol_str
Блог* pinned «#prog #rust #article Как сделать API, оперирующее глобальным состоянием, которое не даёт возможности использовать себя некорректно. Rust особенно хорош в части предотвращения неправильного использования на этапе компиляции: единственная проверка в рантайме…»
#prog #rust #rustasync #rustlib #article

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

В стандартной библиотеке Rust экзекутора нет, для того, чтобы запустить футуру, нужно использовать экзекутор из некой сторонней библиотеки. Де факто в экосистеме Rust сейчас две библиотеки, предоставляющих экзекутор: это tokio и async-std (последнее, вопреки названию, не является стандартной библиотекой и даже не является официальной рекомендацией). При написании асинхронной библиотеки сейчас приходится выбирать между этими двумя крейтами, а написать библиотеку, не привязанную к конкретному экзекутору, практически невозможно, потому что между экзекуторами tokio и async-std хватает различий. Это приводит к расколу в экосистеме, потому что таким образом образуется два параллельных дерева зависимых крейтов, а подружить две библиотеки, использующих два разных экзекутора, толком нельзя.

Найу Мелан¹ такое положение дел не устроило (особенно в силу того, что она делал библиотеку для реализации structured concurrency в Rust), поэтому она создала библиотеку для того, чтобы писать асинхронный код, не завязанный на конкретный экзекутор с незамысловатым именем async_executors. В своём блоге она написала, каким образом этого удалось достичь и с каким препятствиями столкнулась.

blog.wnut.pw/2020/02/25/anouncing-async_executors-a-building-block-for-executor-agnostic-libraries/

¹ Я на самом деле не курсе гендера автора, я лишь спекулирую на имени, поэтому, Найа, если ты вдруг это читаешь и считаешь, что тебя мисгендернули, не надо жаловаться на меня в суд или в поддержку Telegram, пожалуйста
Блог*
#prog #rust #моё Так как я добропорядочный программист, я решил сделать так, чтобы от этого изменения выиграли все и открыл PR в стандартную библиотеку Rust
#prog #rust #моё

Всё сложилось не очень хорошо.

Во-первых, в PR #70366 @cuviper также внёс изменения в iter::Fuse. Теперь вместо I и bool Fuse хранит Option<I>, который выставляется в None, когда нижележащий итератор возвращает None. Для fused итераторов этот Option никогда не выставляется в None, а в ветке, в которой разбираются варианты, для None используется intrinsics::unreachable. Ради изоляции unsafe код ещё и был вынесен в отдельный файл. Этот PR был в итоге смержен (несмотря на очевидное изменение поведения в плане дропа нижележащего итератора), поэтому мне свой PR было проще выкинуть, чем пытаться адаптировать, что я и сделал.

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

В-третьих, в своём новом PR я реализовал подход, предложенный @cuviper: итератор в Fuse теперь хранится в Result<I, StopStateOf<I>>, а смена состояния Fuse делегируется этому ассоциированному типу. Для обычных итераторов это (), и его метод выставляет Result в Err(()), а для fused итераторов это Infallible, и его метод ничего не делает. Таким образом, мы убиваем двух зайцев одним выстрелом: мы убираем ветвление по тегу без unsafe и не храним тег для fused итератора (не говоря уже о том, что мы убираем кучу специализированного кода). Но в бочке мёда нашлась ложка урана: схожий PR 33090, который добавлял специализации для iter::Zip и, в частности, менял поля в зависимости от итераторов был частично откатан в PR 36490 из-за слома обратной совместимости: после этого PR Zip, который раньше был ковариантным, становился инвариантным. Та же проблема не минула и мой PR: нижеприведённый код компилируется до моего PR и не компилируется после:

fn test_fuse_covariant<'a, I>(iter: Fuse<&'static I>) -> Fuse<&'a I> {
iter
}


Выглядит логично: если у нас есть Fuse, в котором лежит долгоживущая ссылка, то мы должны быть в состоянии использовать его там, где ожидается Fuse с ссылкой, живущей меньше. После обновления же код ломается. В принципе, это логично: для компилятора в обобщённом контексте ассоциированный тип непрозрачен, и он не может заранее сказать, какая у него корректная вариантность, поэтому компилятор избирает консервативный подход и предполагает, что ассоциированный тип инвариантен. Что не является логичным, так это то, что добавления ограничения 'static, которое явно говорит о том, что ассоциированный тип не содержит нестатических ссылок, не помогает. Да, это действительно недочёт в компиляторе, но, как мне сказал в дискорде @eddyb, инвариантность важна для обеспечения корректности кода. Ну и, разумеется, способов указать вариантность ассоциированного типа нет ¯\_(ツ)_/¯

В общем, в текущем виде мой PR, скорее всего, не смержат, и когда можно будет это исправить — непонятно. Печально.
Полезная вещь, на мой взгляд. И я припоминаю, что когда-то искал что-то подобное
Forwarded from Shady Bytes
Нашел абсолютный chad метод скинуть время начала чего-либо, с учетом часового пояса человека, который эту страницу откроет.
https://time.is/2100_26_Mar_2020_in_CET?New_post