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

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

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
#prog #rust #моё

Rust, будучи достаточно низкоуровневым языком, тем не менее, даёт возможность использовать замыкания. Эти замыкания не требуют динамической аллокации памяти и могут размещаться на стеке. Вкупе с оптимизатором LLVM это увеличивает шансы на то, что в сгенерированном коде замыкания в явном виде вообще не окажутся. Тем не менее, есть и обратная сторона медали: тип замыкания зависит от того, какие значение оно захватывает, поэтому в Rust каждый литерал замыкания имеет свой собственный тип. Например, код вида let closures = [|x| x + 1, |x| x + 2]; не компилируется.

Один из паттернов, который можно встретить в коде на Rust — это возврат того или иного замыкания в зависимости от некоторого условия. Для обхода ситуации с уникальными типами замыкания есть как минимум два пути:
а) Поместить замыкание в кучу и вернуть указатель на него, а в качестве возвращаемого типа указать что-то вроде Box<dyn Fn(Foo, Bar) -> Baz>. Очевидный недостаток — падение производительности на ровном месте. Более того, выделение памяти в куче и частичное стирание типа препятствует работе оптимизатора.
б) Вернуть замыкание, обёрнутое в один из вариантов некоторого перечисления. Здесь страдает уже эргономика: перечисление приходится разворачивать на стороне вызывающего кода, даже если все варианты имеют одинаковую сигнатуру. С именами также проблема: непонятно, как называть перечисление и его варианты, а различные EitherN не прибавляют наглядности. Можно реализовать нужные Fn-трейты для перечисления самому, но на сегодняшний день этот вариант доступен лишь на nightly.

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

1) Одно и тоже выражение замыкания, но захват разных значений одного и того же типа в зависимости от некоторого условия. Пример:

let func = if some_condition {
|x| x + foo
} else {
|x| x + bar
};

Решение — явный вынос захватываемого значения в отдельную переменную:

let added = if some_condition { foo } else { bar };
let func = |x| x + added;

2) Захват одних и тех же значений, но разные используемые операции. Пример:

let some_var = ...;
let func = if some_condition {
|x| x + some_var
} else {
|x| x * some_var
};

Решение — захват самой операции:

let some_var = ...;
let func = |x| {
if some_condition {
x + some_var
} else {
x * some_var
}
};

Почему захватывается флаг, а не функция? Потому что это влияет на размер получаемого замыкания. В случае захвата функции компилятору ничего не остаётся, кроме как хранить адрес самой функции, который по размеру совпадает с usize, флаг же занимает лишь один байт. Наглядное доказательство.

3) Захват разных значений с разными операциями. Пример (спасибо, @psilon):

let lambda = match a.cmp(&b) {
Less => || x.do_stuff(),
Equal => || y.do_other_stuff(),
Greater => || z.do_stuff_too(),
};

Для этого случая я адаптировал вариант б), с той разницей, что разбор происходит внутри замыкания:

enum Either3<X, Y, Z> {
X(X),
Y(Y),
Z(Z),
}
// или просто
// use either::Either3;
use Either3::*;

let datum = match a.cmp(&b) {
Less => X(x),
Equal => Y(y),
Greater => Z(z),
};
let lambda = || match datum {
X(x) => x.do_stuff(),
Y(y) => y.do_other_stuff(),
Z(z) => z.do_stuff_too(),
};

Разумеется, я не охватил все возможные варианты использования замыканий, но я надеюсь, что это поможет вам меньше боксить в коде только для того, чтобы удовлетворить тайпчекеру.
👍1
Блог*
#prog #rust #моё Rust, будучи достаточно низкоуровневым языком, тем не менее, даёт возможность использовать замыкания. Эти замыкания не требуют динамической аллокации памяти и могут размещаться на стеке. Вкупе с оптимизатором LLVM это увеличивает шансы на…
Я рассмотрел только варианты, когда значение замыкания требуется лишь одно. А что делать, если замыканий требуется несколько, и при этом необходимо, чтобы у них был один тип? Конечно, с версии Rust 1.26.0 замыкания можно клонировать, но это не поможет, если от разных замыканий требуется разное поведение (технически это, конечно, можно обойти путём кастомной имплементации Clone, но это настолько грязный хак, что я его даже рассматривать не буду). С другой стороны, несмотря на то, что литералы замыканий имеют уникальные типы, типы их возвращаемых значений совпадают. В частности, вызов одного и того замыкания с разными аргументами возвращает значение одного и того же типа (в этом Rust отличается от C++, в котором, начиная с C++14, лямбда-функции могут быть полиморфными).

Посмотрим, как можно сделать массив в начале поста:

let make_closure = |added| {
move |x: i32| x + added
};
let closures = [make_closure(10), make_closure(20)];

Аналогичные "фабричные" функции (не обязательно замыкания) можно сделать и для остальных примеров. Естественно, в этом случае несколько страдает эргономика, поскольку ранее захваченные переменные приходится передавать явно, но динамическая аллокация всё же не требуется
#prog #rust #parsing #моё

При написании парсеров часто требуется отдельно отслеживать, когда мы находимся внутри строки, выделенной кавычками, а когда — вне её. На практике это выливается в то, что появляется цикл и отдельный флаг, который меняется в зависимости от рассматриваемого символа. Эта логика затёсывается где-то между остальными строками кода, внося лишнее состояние, мешая восприятию кода и повышая вероятность совершения ошибок при внесении изменений в код. Переделка цикла на итераторы/стримы — это известный способ повысить понятность кода, но в данном случае наличие внешнего состояния не даёт возможность прямолинейно провести эту трансформацию. К счастью, в Rust есть способ инкапсулировать это состояние так, чтобы оно не маячило перед глазами — и для этого нам даже не понадобится itertools. У трейта Iterator есть метод, который можно назвать "map с состоянием". Встречайте — Iterator::scan!

Вот как выглядит начало цепочки итераторов, которая отслеживает, находимся ли мы сейчас внутри строки или нет:
s.chars()
.scan(false, |in_string, ch| {
*in_string ^= ch == '"';
Some((ch, *in_string))
})
.some_other_adapter(...)

Для демонстрации полученного кода рассмотрим функцию, которая ищет символ вне закавыченной строки в строке:

fn find_outside_string(s: &str, needle: char) -> Option<usize> {
// .chars() поменялось на .char_indices()
// для корректного отслеживания позиции.
s.char_indices()
.scan(false, |in_string, (i, ch)| {
*in_string ^= ch == '"';
Some((i, ch, *in_string))
})
.find_map(|(i, ch, in_string)| {
if ch == needle && !in_string {
Some(i)
} else {
None
}
})
}

И немного тестов:

assert_eq!(find_outside_string("Hey!", '!'), Some(3));
assert_eq!(find_outside_string(r#"Outside "Inside!" Outside!"#, '!'), Some(25));
assert_eq!(find_outside_string("Nope", '?'), None);

Как видите, весь код, относящийся непосредственно к отслеживанию строки, инкапсулирован в одном месте и не отвлекает на себя внимания.
Закончили ли мы? Не совсем: в практичных языках нам также требуется возможность экранировать кавычки, чтобы включать кавычки в строки. Обычно это приводит к ещё большему зашумлению кода парсера. Что ж, реализуем поддержку этого требования. Для того, чтобы не слишком усложнять код, будем считать, что кавычки экранируются обратным слешем (\), а сам слеш — двойным повтором символа (\\). Код поменяется не очень сильно: нужно добавить ещё один scan для отслеживания слешей:

s.chars()
.scan(false, |prev_was_slash, ch| {
let escaped = *prev_was_slash;
*prev_was_slash = ch == '\\';
Some((ch, escaped))
})
// если слеш экранирован, то второй символ
// не надо пропускать дальше
.filter_map(|(ch, escaped)| if ch == '\\' && escaped {
None
} else {
Some((ch, escaped))
})
.scan(false, |in_string, (ch, escaped)| {
// экранированые кавычки не переключают строку
if !escaped {
*in_string ^= ch == '"';
}
Some((ch, in_string))
.some_other_adapter(...)

Для демонстрации перепишем find_outside_string с поддержкой экранирования:

fn find_outside_string(s: &str, needle: char) -> Option<usize> {
s.char_indices()
.scan(false, |prev_was_slash, (i, ch)| {
let escaped = *prev_was_slash;
*prev_was_slash = ch == '\\';
Some((i, ch, escaped))
})
.filter_map(|(i, ch, escaped)| {
if ch == '\\' && escaped {
None
} else {
Some((i, ch, escaped))
}
})
.scan(false, |in_string, (i, ch, escaped)| {
if !escaped {
*in_string ^= ch == '"';
}
Some((i, ch, *in_string))
})
.find_map(|(i, ch, in_string)| {
if ch == needle && !in_string {
Some(i)
} else {
None
}
})
}

Проверим, что оно действительно работает:

let s = r#"An "Escaped \" quote" quote"#;
assert_eq!(find_outside_string(s, 'q'), Some(22));
// результат отличается от результата str::find
assert_ne!(find_outside_string(s, 'q'), s.find('q'));

Замечательной особенностью этого паттерна является то, что он без особых усилий расширяется на экранирование других символов.
#prog #python #amazingopensource

Библиотека, которая позволяет визуализировать частично пересекающиеся множества в достаточно наглядном виде. В отличие от традиционных диаграмм Венна, прекрасно масштабируется на большое количество множеств.

https://github.com/gecko984/supervenn
#prog

Шпаргалка по регулярным выражениям. Рисует синтаксические диаграммы, включает в себя интерактивную проверялку. Есть набор часто используемых шаблонов, в число которых, к сожалению, входит e-mail.

https://ihateregex.io
#prog #rust #rustlib

Библиотека для облегчения генерации кода на Rust. Ну, мало ли кого ностальгия по Go замучила.

https://github.com/carllerche/codegen
#gamedev

Пост о том, как упростили кэширование рендеринга ландшафта в FactorIO. Авторы действительно заботятся об игроках со слабыми компьютерами.

https://www.factorio.com/blog/post/fff-333
#gamedev

"This is the book I wish I had when I started making games, and now I want you to have it. "

https://gameprogrammingpatterns.com/

Есть перевод на русский: https://live13.livejournal.com/462582.html
#gamedev

Прототип игры в 3D-ASCII графике. Вещь несколько бессмысленная, но впечатляющая.

https://www.squidi.net/threep/p083/
#prog #rust

Чтобы ускорить сборку своего проекта, достаточно всего лишь... Читать продолжение
#prog #rust #gamedev

Потрясающий туториал по созданию своего рогалика на расте. Уже больше 70 глав!

https://bfnightly.bracketproductions.com/rustbook/chapter_0.html
#quotes (автор, к сожалению, неизвестен)
Forwarded from The Wacky Yellow Dog
@aikidos @Psilon

THE RESTRICT CONTRACT
I, [insert your name], a PROFESSIONAL or AMATEUR [circle one] programmer recognize that there are limits to what a compiler can do. I certify that, to the best of my knowledge, there are no magic elves or monkeys in the compiler which through the forces of fairy dust can always make code faster. I understand that there are some problems for which there is not enough information to solve. I hereby declare that given the opportunity to provide the compiler with sufficient information, perhaps through some key word, I will gladly use said keyword and not bitch and moan about how «the compiler should be doing this for me.»

In this case, I promise that the pointer declared along with the restrict qualifier is not aliased. I certify that writes through this pointer will not effect the values read through any other pointer available in the same context which is also declared as restricted.
#meme

Когда в программисткий чатик заходит девушка