Size: a a a

2020 February 27
Блог*
dereference_pointer_there
#prog #article #gamedev

Статья о том, как портировали на консоли игру, первоначально написанную на JavaScript. Спойлер: динамичность мешается.

habr.com/ru/post/489974/
Фактически это пересказ вот этого выступления.
источник
Блог*
#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(),
};

Разумеется, я не охватил все возможные варианты использования замыканий, но я надеюсь, что это поможет вам меньше  боксить в коде только для того, чтобы удовлетворить тайпчекеру.
источник
Блог*
dereference_pointer_there
#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(),
};

Разумеется, я не охватил все возможные варианты использования замыканий, но я надеюсь, что это поможет вам меньше  боксить в коде только для того, чтобы удовлетворить тайпчекеру.
Я рассмотрел только варианты, когда значение замыкания требуется лишь одно. А что делать, если замыканий требуется несколько, и при этом необходимо, чтобы у них был один тип? Конечно, с версии 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)];

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

При написании парсеров часто требуется отдельно отслеживать, когда мы находимся внутри строки, выделенной кавычками, а когда — вне её. На практике это выливается в то, что появляется цикл и отдельный флаг, который меняется в зависимости от рассматриваемого символа. Эта логика затёсывается где-то между остальными строками кода, внося лишнее состояние, мешая восприятию кода и повышая вероятность совершения ошибок при внесении изменений в код. Переделка цикла на итераторы/стримы — это известный способ повысить понятность кода, но в данном случае наличие внешнего состояния не даёт возможность прямолинейно провести эту трансформацию. К счастью, в 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'));

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

Semver trick, или как обновить библиотеку лишь частично.

https://github.com/dtolnay/semver-trick
источник
Блог*
#prog #amazingopensource

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

https://github.com/gecko984/supervenn
источник
Блог*
#prog

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

https://ihateregex.io
источник
Блог*
#prog #rust

Библиотека для облегчения генерации кода на 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. "

http://gameprogrammingpatterns.com/

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

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

http://www.squidi.net/threep/p083/
источник
Блог*
#prog #rust

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

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

http://bfnightly.bracketproductions.com/rustbook/chapter_0.html
источник
Блог*
dereference_pointer_there
#rust #gamedev

С безукоризенной пунктуальностью выкладываю выпуск новостей раст-геймдева за декабрь. Между прочим, пока что самый интересный.

https://rust-gamedev.github.io/posts/newsletter-005/
#rust #gamedev

Последующие выпуски:
https://rust-gamedev.github.io/posts/newsletter-006/
https://rust-gamedev.github.io/posts/newsletter-007/ (совсем свежий, всего вчера опубликован!)
источник
Блог*
Точность — это моё второе имя. Второе, а не первое.
источник
Блог*
#quotes (автор, к сожалению, неизвестен)
источник
Блог*
Переслано от Doge Shibu
@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
источник
Блог*
#meme

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