Size: a a a

2020 December 05
Блог*
Ну и в годовщину имеет смысл устроить небольшое голосование. Я отобрал пачку наиболее достойных, на мой взгляд, авторских постов, а теперь предлагаю судить вам о том, какой из них лучший. Вот они (ибо ссылки в опросах не работают):

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

⬇️⬇️⬇️⬇️
источник
Блог*
Лучший пост?
Окончательные результаты
15%
Трейт, гарантирующий нулевой размер
9%
Форматировщики даты
13%
Хроники замыканий
6%
'_ and friends
17%
Абьюз Deref
11%
Макрос для перевода численных констант в строковой литерал
29%
Регуляр_очки
Проголосовало: 89
источник
2020 December 06
Блог*
#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 наши строки — и компилятор всё прекрасно проверяет за нас!
Telegram
Блог*
#prog #rust #моё

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

Итак, как же нам сравнить две строки, игнорируя регистр символов? Большинство Rust-программистов (особенно новичков) напишут что-то вроде этого:

fn equal_ignoring_case(a: &str, b: &str) -> bool {
   a.to_lowercase() == b.to_lowercase()
}

Правильное ли это решение? НЕТ, НЕПРАВИЛЬНОЕ, КТО ВООБЩЕ ТАК ПИШЕТ Технически оно верное, но оно делает много лишней работы. Что тут происходит? Сначала под первую строку выделяется место в куче, которое заполняется проходом по строке с преобразованиями по довольно нетривиальным правилам, во время которых выполняется бинарный поиск по захардкоженным таблицам, затем то же самое…
источник
Блог*
Отлично, теперь переходим к самому вкусному: написанию макроса! Нам нужно разобрать 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

Всё работает, как и ожидалось! Как всегда, весь код в гисте.
Telegram
Блог*
#prog #rust #моё

В Rust есть такая удобная вещь, как сопоставление с образцом (pattern matching), и она работает в том числе и для строк. К сожалению, оно позволяет сопоставлять только строки целиком, но не по частям. В частности (no pun intended), match не позволяет разделить строку на некоторый фиксированный префикс и всё остальное.

Или всё же позволяет? В конце-концов, можно написать так:

match str_value.as_bytes() {
   [b'p', b'r', b'e', b'f, b'i', b'x', rest @ ..] => {}
   _ => {}
}

, и тут даже будет помогать компилятор — он подскажет нам, если мы будем дважды проверять один и тот же префикс. Но тут есть и недостатки: остаток строки (rets во второй строчке) — не &str, а &[u8], ну и, конечно, это довольно неудобно писать. Первый недостаток отчасти перекрывается str::get_unchecked/std::str::from_utf8_unchecked — отчасти, поскольку в паттерн байта можно написать и часть многобайтового символа, а вот второй недостаток обойти сложнее. В идеале мы бы хотели написать матч в виде сопоставления части строки…
источник
Блог*
Как же хочется временами простого человеческого "Вот тебе деньги, возьми их"
источник
2020 December 07
Блог*
#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 не может не радовать.
источник
Блог*
Если быстро выдернуть чеку у гранаты и поднести ухо к дыре, где была чека, можно услышать, как увеличивается средний мировой IQ.
источник
2020 December 08
Блог*
источник
2020 December 09
Блог*
#prog #cpp

Длиннющий тред с примерами undefined behavior в C++. По состоянию на этот день в нём 98 записей
источник
Блог*
источник
Блог*
В разработке пост с рабочим названием "как быть, если тебе нужно написать простенький парсер, но nom выглядит перебором"
источник
Блог*
Crate was updated: err_or#0.1.0 [docs.rs] [crates.io] [lib.rs]
источник
Блог*
А я тут маленький крейтик релизнул 👀

Он добавляет методы Option::{err_or,err_or_else} аналогичные к Option::{ok_or,ok_or_else}
источник
Блог*
xxx:

    // This call is safe since ...
   unsafe { ... }

yyy:

.. tomorrow (простите)

#трудовыебудни
источник
Блог*
#prog #suckassstory

1С — это не просто язык, это образ мышления.
источник
Блог*
Когда у меня спрашивают, почему я не люблю одинэсников. Хотя бы, блять, за это.
Из рабочей переписки. В общем, базу товаров синхронизируем в 1С и мускуле.
источник