1.83K subscribers
3.29K photos
130 videos
15 files
3.57K links
Блог со звёздочкой.

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

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
Итак, сегодня 5 декабря. Чем примечателен этот день? Это — день, когда Блог* появился на свет.

Тогда мои амбиции были невелики: я считал, что этот блог будут читать только моя семья и ближайшие знакомые — полтора-два десятка человек максимум. Собственно, само название, как и логотип, я считал временным — даже думал в какой-то момент его поменять. Оглядываясь назад, легко обвинить меня в недальновидности — но с другой стороны, кто тогда мог подумать, что канал с мемасиками и заметками по расту станет... Не популярным, но всё же достаточно известным?

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

Разумеется, я повлиял на канал просто в силу того, что я являюсь его единственным автором, но вот повлиял ли он на меня? На самом деле — да. По себе я знаю, что я человек, который относительно быстро увлекается чем-то новым, но так же быстро к этому охладевает, и потому я опасался, что быстро забью на блог и тот просто умрёт. Именно поэтому я дал себе обещание выкладывать на канале хоть что-то каждый день. Те, кто подписан на Блог* достаточно давно, знают, что в полной мере я сдержать это обещание не смог (некоторые дни я всё же пропускал), но всё же мой план сработал — то, что вы сейчас читаете этот пост, является живым тому подтверждением. Я целый год ежедневно занимался чем-то исключительно по собственной прихоти. Это может показаться незначительным, но для меня — человека со слабой волей — это стало достижением.

Ещё более удивительным для меня стал тот факт, что за этот год я написал в общей сложности — так, дайте-ка посчитаю — 23 оригинальных авторских поста, причём без какого-то специального намерения. Я даже сделал на Хабре перевод весьма крутой и крупной статьи, сделав себя несколько заметным и за пределами Telegram. Если бы мне в начале предыдущего года сказали бы, что я столько напишу — я бы не поверил. Блог* не только замотивировал меня заниматься им ежедневно, но и раскрыл писательскую жилку. Одним словом, он повлиял на меня куда сильнее, чем я ожидал, когда выложил тот мем с жирафом.

Но, впрочем, достаточно разговоров обо мне. Своим нынешним состоянием Блог* обязан и другим людям.
Один из них — это Олег Ковалёв (@olegkovalov), ведущий @oleg_log. Именно он, обладающий поистине гигантским терпением, терпеливо выслушивал мои замечания в духе "А вот на расте!..", проявил искренний интерес к моему блогу, сделал первый, кажется, публичный репост и, подкинув мне идею для поста, помог мне набрать первую сотню подписчиков. Олег, если ты это читаешь — спасибо тебе большое.
Другой человек — это... Тоже Олег, но другой, который ведёт небезызвестный Профунктор (я ещё застал время, когда у него была ссылка @libmustdie). Он организовал подборку, в которую попал в том числе и канал вашего (не)покорного слуги, и разом нагнал мне больше сотни подписчиков. И тебе, Олег, спасибо (а также Моноид-тян).
Также я благодарен множеству других людей — владельцам каналов, которые репостили отдельные посты и тепло отзывались о канале (Рома, я типа до сих пор помню твой комплимент). Это очень греет душу и помогает осознавать, что я стараюсь над этим каналом не ради себя одного.

Но главные, кому я хочу сказать спасибо — это моим подписчикам (слишком многочисленным, чтобы перечислить их тут поимённо). Тот факт, что вы читаете мой канал — и даёте мне ценный фидбек — помог мне перестать относиться к каналу как к какой-то блажи и начать относиться к нему как чему-то, что имеет ценность для многих людей — куда большего числа, чем с которым я бы мог познакомиться лично.
Этот день рождения Блог*а — первый, но, надо думать, отнюдь не последний. Пусть в следующий раз я смогу похвастать уже тысячами подписчиков (впрочем, я не гонюсь в первую очередь за количеством — пусть их будет поменьше, но они будут действительно читать мой блог!), пусть в следующем году оригинального контента станет больше и пусть Блог* станет уважаемым СМИ, на которое будут ссылаться международные издания. Спасибо, что довели меня до этого.

Отпразднуем снова через год! Ура!
Блог* pinned «Итак, сегодня 5 декабря. Чем примечателен этот день? Это — день, когда Блог* появился на свет. Тогда мои амбиции были невелики: я считал, что этот блог будут читать только моя семья и ближайшие знакомые — полтора-два десятка человек максимум. Собственно,…»
Ну и в годовщину имеет смысл устроить небольшое голосование. Я отобрал пачку наиболее достойных, на мой взгляд, авторских постов, а теперь предлагаю судить вам о том, какой из них лучший. Вот они (ибо ссылки в опросах не работают):

1. Реализация трейта, гарантирующего нулевой размер Self.
2. Написание zero-cost (ну, почти) форматировщиков даты.
3. Эпические "Хроники замыканий" в трёх частях: раз, два, три.
4. Рассказ о lifetime elision и анонимном лайфтайме ('_).
5. Как написать код, за который вас возненавидят коллеги (или о том, как можно абьюзить Deref).
6. Конструирование макроса, переводящего численные константы в строки на этапе компиляции.
7. Тонкий и глубокий анализ недостатков регулярных выражений (aka "Да не бомбит у меня!").

⬇️⬇️⬇️⬇️
Блог* pinned «Ну и в годовщину имеет смысл устроить небольшое голосование. Я отобрал пачку наиболее достойных, на мой взгляд, авторских постов, а теперь предлагаю судить вам о том, какой из них лучший. Вот они (ибо ссылки в опросах не работают): 1. Реализация трейта, гарантирующего…»
#prog #rust #моё

Вы что, правда думали, что за всеми празднествами я оставлю вас без поста? Не скрою, идея заманчивая, но я решил ей не поддаваться... Хотя к тому моменту, как я закончу писать этот пост, уже наверняка наступит 6 декабря... Впрочем, достаточно прелюдий — переходим к постановке задачи!

Иногда нам требуется сопоставить значение с одной из строк... Но при этом игнорируя регистр символов. В общем случае это довольно сложная задача, и даже не из-за зависимости от локали, а просто от сложности правил перевода символов из одного регистра в другой. Пока что забьём на это и будем рассматривать только ASCII-строки. Что нам требуется? Чтобы:
а) чтобы можно было сопоставить (ASCII) строку, невзирая на её регистр;
б) чтобы нас предупреждал компилятор о перекрывающихся паттернах (а вот это уже интересно — компилятор требует точного совпадения паттернов для проверки);
в) чтобы по возможности сохранить возможности, предоставляемые match.

Итак, как нам проверить, что строки неодинаковы с точностью до регистра? В принципе, можно сделать уже известным способом const assert, используя соответствующие const fn, но так как я человек ленивый, я пойду по лёгкому пути: я проверю, что все паттерны на самом деле в нижнем регистре, а проверить их уникальность оставлю компилятору.

Итак, переходим к подзадаче: убедиться, что строка состоит из символов ASCII, но в нижнем регистре, на этапе компиляции. Правда, так как мы хотим использовать в паттернах не только буквы, но и, скажем, цифры, правильнее сказать "из символов ASCII не в верхнем регистре". Для решения части "на этапе компиляции" воспользуемся уже знакомым трюком, который я тут вроде уже показывал: заведём новую константу типа [(); 1], а в качестве значения ей присвоим [(); condtion as _], где condition — условие, которое нам нужно проверить. Если condition вычисляется в true (и вычисляется на этапе компиляции в принципе), то as _ приводит булево значение к 1usize, получая выражение [(); 1], соответствующее типу. В противном случае false приводится к 0 и выражение принимает вид [(); 0], вызывая ошибку компиляции из-за несовпадения типов. Теперь всё, что нам остаётся для решения это подзадачи — написать функцию, которую можно вызвать на этапе компиляции и которая проверяет указанное выше условие. Написать такую функцию несколько неудобно из-за ограничений const fn (в частности, мы не можем использовать итераторы), но вполне возможно:

const fn is_ascii_lowercase(s: &str) -> bool {
let s = s.as_bytes();
let len = s.len();
let mut i = 0;
while i < len {
if !s[i].is_ascii() || s[i].is_ascii_uppercase() {
return false;
}
i += 1;
}
true
}

Ладно, а как нам проверить, что несколько строк записаны в ASCII lowercase? Ну как-как, принимаем список и проходимся о нему:

const fn are_all_ascii_lowercase(ss: &[&str]) -> bool {
let len = ss.len();
let mut i = 0;
while i < len {
if !is_ascii_lowercase(&ss[i]) {
return false;
}
i += 1;
}
true
}

Окей, с этой подзадачей мы разобрались. Как нам теперь убедиться, что все строки разные? А эту задачу мы уже решали: генерируем функцию, которая разбирает строку, и подсовываем в match наши строки — и компилятор всё прекрасно проверяет за нас!
Отлично, теперь переходим к самому вкусному: написанию макроса! Нам нужно разобрать match, так что начнём с этого:

macro_rules! ascii_case_insensitive {
(match $value:ident {
$(... ,)*
_ => $catch_all:expr $(,)?
}) => { ... }
}

А теперь на минуту остановимся и подумаем, что из себя представляет паттерн, который мы пытаемся разобрать. В прошлый раз я совершенно упустил из виду, что обычно мы можем перечислить несколько паттернов, разделив их |, равно как и то, что паттерн может также предваряться |. Таким образом, корректный кусок макроса для распознавания паттернов должен выглядеть так:

$(|)? $($pattern:literal)|+

$(|)? отвечает за опциональную черту в начале. $pattern:literal говорит, что $pattern — это литерал, а $(...)|+ говорит о том, что то, что внутри скобок, повторяется один или более раз, и что повторы разделены |. Но постойте-ка, есть же ещё и опциональное охранное выражение! С учётом всего этого паттерн для одной ветви принимает такой вид:

$(|)? $($pattern:literal)|+ $(if $condition:expr)? => $arm:expr,

Отлично, с разбором мы справились (правда, всё так же упустив возможность привязать имена к паттернам). Что мы со всем этим делаем? Мы проверяем, что все строки в нижнем регистре:

#[deny(const_err)]
const _ARE_ALL_ASCII_LOWERCASE: [(); 1] = [(); are_all_ascii_lowercase(&[$($($pattern,)+)*]) as _];

И что они все разные:

#[allow(dead_code)]
fn non_repeating(s: &str) {
#[deny(unreachable_patterns)]
match s {
$($(| $pattern)+ => (),)*
_ => (),
}
}

А что нам делать непосредственно самой проверкой? Мы проверяем, что значение равно, за вычетом ASCII-регистра, одному из паттернов... И что охранное выражение также справедливо, если оно есть:

x if ($(x.eq_ignore_ascii_case($pattern))||+) $(&& $condition)? => $arm,

Обратите внимание, здесь мы повторяем (+) выражения для паттернов, разделив их ||.

Что ж, давайте опробуем макрос в действии:

#[derive(Debug)]
enum Example {
Foo,
Bar,
FourtyTwo,
}

impl std::str::FromStr for Example {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(ascii_case_insensitive!(match s {
"foo" => Self::Foo,
"bar" if s.as_bytes()[0].is_ascii_lowercase() => Self::Bar,
"fourtytwo" | "fourty_two" | "42" => Self::FourtyTwo,
_ => return Err(s.into()),
}))
}
}

fn main() {
let inputs = [
"foo",
"Foo",
"FOO",
"bar",
"bAr",
"BAR", // ошибка, первый символ в верхнем регистре
"fourtytwo",
"Fourtytwo",
"FOURTYTWO",
"fourty_two",
"fOuRtY_tWo",
"42",
"bogus",
];
for &input in &inputs[..] {
println!("{:?}", input.parse::<Example>());
}
}

Эта программа выдаёт следующее:

Ok(Foo)
Ok(Foo)
Ok(Foo)
Ok(Bar)
Ok(Bar)
Err("BAR")
Ok(FourtyTwo)
Ok(FourtyTwo)
Ok(FourtyTwo)
Ok(FourtyTwo)
Ok(FourtyTwo)
Ok(FourtyTwo)
Err("bogus")

...как и ожидалось. Что будет, если мы попытаемся сделать два одинаковых паттерна? Скажем, так:

            ...
"foo" | "foo" => Self::Foo,
...

Компилятор жалуется:

error: unreachable pattern

А если один из паттернов не в нижнем регистре:

            ...
"Foo" => Self::Foo,
...

то компилятор опять жалуется:

error[E0308]: mismatched types

Всё работает, как и ожидалось! Как всегда, весь код в гисте.
Как же хочется временами простого человеческого "Вот тебе деньги, возьми их"
#prog #article

Обзор истории систем контроля версий с разбором их внутренних устройств в двух частях: первая, вторая.

В тему также интервью с Pierre-Étienne Meunier, ведущим разработчиком Pijul.
#prog #rust #article

Статья о том, чего бы автору хотелось видеть для Rust в 2021 году. Всё ключевые моменты выделять не буду (а иначе зачем я вам ссылку даю?), выделю только то, что привлекло моё внимание:

* I want to see Rust shed some of its reputation for being hard to learn

Согласно автору, новички сталкиваются с повышенной нагрузкой при изучении языка, поскольку им приходится учить и сам язык, и то, как писать на нём идиоматичный код. Учить Rust сложно ввиду того, что он схож с мейнстримными ЯП, но при этом довольно сильно от них отличается — достаточно, чтобы предыдущий опыт был не слишком полезен. Одним из следствий этих отличий является то, что лучшие практики из одних языков считаются антипаттернами в Rust (pub/sub, observer pattern), равно как и наоборот (затенение переменных). Автор считает, что новичкам имеет смысл позволить себе писать сначала неаккуратный и/или неидиоматичный код, а улучшать его уже потом. Да, это вопрос чисто психологический, но в интернете чаще выкладывают хороший код и редко — скажем так, не очень хороший, что создаёт определённое психологическое давление. Именно это и пишет автор:

I am not exactly sure how to create the conditions for this outcome. Maybe more people can publish more Rust that looks messy but “just works”.

Собственно, как совершенно справедливо заметил trentj на URLO (что в итоге стало фразой недели в TWiR №360):

"Just because Rust allows you to write super cool non-allocating zero-copy algorithms safely, doesn’t mean every algorithm you write should be super cool, zero-copy and non-allocating."

* More blog posts from developers and management using Rust at work

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

It’s great to hear Rust used in complicated, low-level development, but it would be normalizing to read about more trivial types of applications, just as a way to highlight Rust as being good for general purpose use.

Я со своей стороны могу лишь добавить, что по мере возможности стараюсь закрывать этот пробел в русскоязычном сообществе Telegram. Некоторые из подобных постов на моём канале можно найти по хештегу #successstory.

* More shared experiences from people picking up Rust as a second language.

Во многом перекликается с первым тезисом. Согласно автору, это может поспособствовать двум вещам: показать, что для того, чтобы выучить Rust, не нужно быть каким-то выдающимся человеком, и раскрыть глаза на вещи, которые опытные Rust-разработчики не замечают или воспринимают как должное.

I believe content from this crowd would be my favorites to read since I think they’ll give valid opinions to someone who has been using Rust for years (like me) now overlook or accept without second thoughts.
#prog #rust #article

Тем временем народ настолько звереет от нехватки анонимных сумм-типов в Rust (а RFC для них было немало — Вафель не даст соврать), что пишет свои. В этот раз получилось даже неплохо.
#prog #rust #article

Небольшая заметка о том, как можно ограничить видимость реализации трейта, используя исключительно имеющиеся возможности системы типов Rust.
#prog #go #article

Статья о внутреннем устройстве map в Go. К сожалению, в статье длиннющая преамбула о различных вариантах реализации хэш-таблиц в разных языках и крайне мало о собственно реализации в Go.

Ключевая фишка реализации — фактически нетипизированная реализация мапы как таковой, отвечающая структуре hmap. Поле buckets имеет тип unsafe.Pointer, который является аналогом void* из C: может указывать на что угодно. В контексте Go примечателен ещё и тем, что, в отличие от встроенных указателей, он не отслеживается сборщиком мусора. Все мало-мальски интересные функции, манипулирующие map, принимают также указатель на значение типа maptype. В нем описаны характеристики хэш-таблицы — такие, как размер ключей, значений, бакетов, хранятся ли они значения по месту или же хранятся лишь указатели на них, а также функция для хэширования ключей и (через поле тип type_) функции для сравнения значений ключей и значений. Откуда берутся значения для maptype? А их генерирует компилятор автоматически, когда переписывает обращения к map через функции типа mapaccess1/mapaccess2. На этапе же компиляции он может проверить, что для типа ключа map определены операции хэширования и сравнения.

Не знаю, как вам, а мне эта картина кажется весьма шаткой, особенно с учётом комментариев вроде "сохраняйте это определение структуры согласованным с вот этим местом в реализации рефлексии и той части компилятора, которая обходит AST". Ну и дублирование кода между mapaccess1/mapaccess2 не может не радовать.
Forwarded from Generative Anton (Anton Repushko)
Если быстро выдернуть чеку у гранаты и поднести ухо к дыре, где была чека, можно услышать, как увеличивается средний мировой IQ.
В разработке пост с рабочим названием "как быть, если тебе нужно написать простенький парсер, но nom выглядит перебором"