Size: a a a

2020 February 13
Блог*
#gamedev

Вы когда-нибудь хотели прогуляться по трёхмерному фракталу? Что ж, теперь вы можете.
Всё ещё в разработке, но выглядит потрясающе.

https://bananaft.itch.io/yedomaglobula
источник
Блог*
dereference_pointer_there
#gamedev

Вы когда-нибудь хотели прогуляться по трёхмерному фракталу? Что ж, теперь вы можете.
Всё ещё в разработке, но выглядит потрясающе.

https://bananaft.itch.io/yedomaglobula
источник
Блог*
dereference_pointer_there
Телеграм зашакалил фотку :/
В нормальном качестве можно посмотреть по ссылке
источник
2020 February 14
Блог*
#quotes
источник
Блог*
Переслано от Fail Cascade
я кстати недавно видел вывеску SPA салон и подумал что это вебстудия
источник
Блог*
#prog

Vlang — "Мы всегда стучим снизу"

https://github.com/vlang/v/issues/3707
источник
2020 February 15
Блог*
Нужно ли тестировать приватный функционал (предполагается, что у вас есть способ делать это без хаков и костылей)?
Анонимный опрос
66%
Да
20%
Нет
14%
Всё не так однозначно (в чат канала)
Проголосовало: 733
источник
Блог*
dereference_pointer_there
Нужно ли тестировать приватный функционал (предполагается, что у вас есть способ делать это без хаков и костылей)?
Анонимный опрос
66%
Да
20%
Нет
14%
Всё не так однозначно (в чат канала)
Проголосовало: 733
Буду признателен, если вы поделитесь этим опросом. Я хочу узнать мнение людей помимо подписчиков каналов
источник
Блог*
dereference_pointer_there
Нужно ли тестировать приватный функционал (предполагается, что у вас есть способ делать это без хаков и костылей)?
Анонимный опрос
66%
Да
20%
Нет
14%
Всё не так однозначно (в чат канала)
Проголосовало: 733
Забыл упомянуть: приватный функционал считается достаточно нетривиальным, чтобы его тестирование имело смысл. Ну, так, на всякий случай.
источник
2020 February 16
Блог*
источник
2020 February 18
Блог*
Если вы думаете, что я обленился и забыл про блог, то вы совершенно правы вот вам тизер завтрашней статьи:
источник
Блог*
#meme #моё #prog #rust

You vs the guy she told you not to worry about
источник
Блог*
источник
2020 February 19
Блог*
#моё #prog #rust

В крейте time есть структура Date с методом format, который возвращает String со датой, отформатированной согласно переданному формату. Выглядит это примерно так (пример из документации):

assert_eq!(date!(2019-01-02).format("%Y-%m-%d"), "2019-01-02");

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

Как примерно должен выглядеть код? Нам нужно абстрагироваться от конкретных типов по операции "отформатируй сюда дату", чтобы из элементарных форматировщиков можно было собрать составной. В самой операции нам, очевидно, требуются: сам форматировщик, дата и буфер, куда будут записываться данные. Мы не хотим возвращать строку непосредственно, потому что это при объединении привело бы ко множеству ненужных мелких аллокаций. Также мы не хотим, чтобы форматировщики могли удалять из буфера данные, поэтому мы сделаем обёртку над строкой с более узким интерфейсом:

use std::fmt;

#[derive(Default)]
pub struct Buf {
   inner: String,
}

impl Buf {
   pub fn new() -> Self {
       Self::default()
   }
   
   pub fn append(&mut self, s: &str) {
       self.inner += s;
   }
   
   // Этот метод позволит нам использовать макрос `write!` на `Buf`.
   // Он позволяет не безусловно выделять новую строку,
   // а использовать место в уже имеющейся
   pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
       self.inner.write_fmt(args)
   }
}

Сам общий интерфейс форматировщиков:

use std::fmt;
use time::Date;

pub trait FormatDate {
   fn format_date(&self, b: &mut Buf, d: Date) -> fmt::Result;
}

(проблем с тем, чтобы принимать Date по значению, нет, потому что это Copy-тип).

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

impl FormatDate for &'_ str {
   fn format_date(&self, b: &mut Buf, _: Date) -> fmt::Result {
       b.append(self);
       Ok(())
   }
}

Теперь напишем реализацию для, скажем, дня даты:

pub struct DayOf;

impl FormatDate for DayOf {
   fn format_date(&self, b: &mut Buf, d: Date) -> fmt::Result {
       write!(b, "{:02}", d.day())
   }
}

Формат "{:02}" означает, что для печати дня отведено два места, и если в номере дня всего одна цифра, то вывод будет дополнен слева нулями. Форматировщики для номера месяца и года пишутся аналогично, поэтому не будем подробнее на этом останавливаться.
источник
Блог*
Как же теперь объединить всё это? Написать реализацию для кортежей форматировщиков, разумеется! Так как вариадических обобщённых типов в Rust нет, а руками писать реализации мне лень, напишем макрос:

macro_rules! impl_for_tuples {
   () => {};
   ($head:ident $(, $rest:ident)*) => {
       impl<$head, $($rest),*> FormatDate for ($head, $($rest,)*)
       where
           $head: FormatDate,
           $($rest: FormatDate,)*
       {
           fn format_date(&self, b: &mut Buf, date: Date) -> fmt::Result {
               #[allow(non_snake_case)]
               let &(ref $head, $(ref $rest,)*) = self;
               $head.format_date(b, date)?;
               $($rest.format_date(b, date)?;)*
               Ok(())
           }
       }
       impl_for_tuples!($($rest),*);
   };
}

impl_for_tuples!(A, B, C, D, E, F, G, H);

Добавим для удобства extension trait:

pub trait DateExt {
   fn format_into<F: FormatDate>(self, b: &mut Buf, f: F) -> fmt::Result;
   fn format_as<F: FormatDate>(self, f: F) -> String;
}

impl DateExt for Date {
   fn format_into<F: FormatDate>(self, b: &mut Buf, f: F) -> fmt::Result {
       f.format_date(b, self)
   }
   fn format_as<F: FormatDate>(self, f: F) -> String {
       let mut buf = Buf::default();
       let _ = self.format_into(&mut buf, f);
       buf.inner
   }
}

И проверим, как работает:

use time::date;

// Очень удобный макрос
let d = date!(2020-02-18);
let format = (DayOf, "/", MonthOf, "/", YearOf);
// Обратите внимание на ноль впереди в месяце
assert_eq!(d.format_as(format), "18/02/2020");

Работает!
источник
Блог*
Продолжение следует...
источник
Блог*
dereference_pointer_there
Как же теперь объединить всё это? Написать реализацию для кортежей форматировщиков, разумеется! Так как вариадических обобщённых типов в Rust нет, а руками писать реализации мне лень, напишем макрос:

macro_rules! impl_for_tuples {
   () => {};
   ($head:ident $(, $rest:ident)*) => {
       impl<$head, $($rest),*> FormatDate for ($head, $($rest,)*)
       where
           $head: FormatDate,
           $($rest: FormatDate,)*
       {
           fn format_date(&self, b: &mut Buf, date: Date) -> fmt::Result {
               #[allow(non_snake_case)]
               let &(ref $head, $(ref $rest,)*) = self;
               $head.format_date(b, date)?;
               $($rest.format_date(b, date)?;)*
               Ok(())
           }
       }
       impl_for_tuples!($($rest),*);
   };
}

impl_for_tuples!(A, B, C, D, E, F, G, H);

Добавим для удобства extension trait:

pub trait DateExt {
   fn format_into<F: FormatDate>(self, b: &mut Buf, f: F) -> fmt::Result;
   fn format_as<F: FormatDate>(self, f: F) -> String;
}

impl DateExt for Date {
   fn format_into<F: FormatDate>(self, b: &mut Buf, f: F) -> fmt::Result {
       f.format_date(b, self)
   }
   fn format_as<F: FormatDate>(self, f: F) -> String {
       let mut buf = Buf::default();
       let _ = self.format_into(&mut buf, f);
       buf.inner
   }
}

И проверим, как работает:

use time::date;

// Очень удобный макрос
let d = date!(2020-02-18);
let format = (DayOf, "/", MonthOf, "/", YearOf);
// Обратите внимание на ноль впереди в месяце
assert_eq!(d.format_as(format), "18/02/2020");

Работает!
#моё #prog #rust

Так можем ли мы сделать лучше? Определённо, ведь у нашего решения есть ряд недостатков:
1. Захардкоженные символы для формирования отступов. Не всегда в начале требуются нули.
2. Захардкоженные ширины отступов. Мы не можем легко поменять их.
3. Потенциальная просадка по производительности.  В документации к методу Date::month_day сказано, что использовать его эффективнее, чем доставать месяц и день по отдельности. Наш код этого не учитывает.

Третий пункт решается относительно просто: мы делаем у трейта параметр, который и является данными, из которых изымаются компоненты:

pub trait FormatDate<D> {
   fn format_date(&self, b: &mut Buf, d: D) -> fmt::Result;
}

Для удобства сделаем алиас на возвращаемый тип Date::as_ymd: pub type Ymd = (i32, u8, u8);. Если тип умеет форматировать дату в деконструированном виде (Ymd), то он может форматировать и исходную дату:

impl<T: FormatDate<Ymd>> FormatDate<Date> for T {
   fn format_date(&self, b: &mut Buf, date: Date) -> fmt::Result {
       self.format_date(b, date.as_ymd())
   }
}

С первыми двумя недочётами разобраться несколько сложнее. Посмотрим на то, как, по идее, выглядит реализация FormatDate<Ymd>:

impl FormatDate<Ymd> for SomeFormatter {
   fn format_date(&self, b: &mut Buf, ymd: Ymd) -> fmt::Result {
       let part = some_part_of_date(ymd);
       write!(b, "{:0width$}", part, width = SOME_WIDTH)
   }
}
(справка по синтаксису форматных строк)

Для того, чтобы сделать форматировщик более настраиваемым, нам нужно разделить эту схему форматирования на отдельные части. Этими частями являются:
1. Функция, возвращающая "часть" даты, которая реализует Display;
2. Ширина поля под отформатированные данные;
3. Символ для заполнения незанятой части поля.

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

Выделение нужной части:

pub trait Extract<Date = Ymd> {
   type Output: fmt::Display;
   fn extract(ymd: Date) -> Self::Output;
}

Пример реализации:

pub struct DayOf;

impl Extract for DayOf {
   type Output = u8;
   fn extract((_year, _month, day): Ymd) -> u8 {
       day
   }
}

Немного более сложный пример:

pub struct MonthFullWordOf;
impl Extract for MonthFullWordOf {
   type Output = &'static str;
   fn extract((_year, month, _day): Ymd) -> &'static str {
       match month {
           1  => "January",
           2  => "February",
           3  => "March",
           4  => "April",
           5  => "May",
           6  => "Juny",
           7  => "July",
           8  => "August",
           9  => "September",
           10 => "October",
           11 => "November",
           12 => "December",
           // Здесь нормально паниковать, потому что месяц другие номера иметь не может.
           // В типах это, к сожалению, не выражено.
           _  => unreachable!(),
       }
   }
}

Ширина поля, по хорошему, должна быть константой, но параметризовывать типы значениями в Rust на stable пока нельзя. Не то, чтобы меня это остановило, но в данном случае проблемы решается достаточно просто и без const generics:

pub trait Width {
   const WIDTH: usize;
}

// Пример реализации
pub struct W2;
impl Width for W2 {
   const WIDTH: usize = 2;
}

Наш составной форматировщик в итоге выглядит следующим образом:

pub struct Formatter<Extractor, Width, Padding = NoPad>(
   std::marker::PhantomData<(Extractor, Width, Padding)>,
);
источник
Блог*
Так как абстрагироваться от символов заполнения толком нельзя, мы просто сделаем несколько несколько маркерных типов и напишем конкретные реализации FormatDate для Formatter, параметризованных различными заполнениями:

pub struct PadZeros;
pub struct NoPad;

impl<Extractor, W> FormatDate<Ymd> for Formatter<Extractor, W, PadZeros>
where
   Extractor: Extract,
   W: Width,
{
   fn format_date(&self, b: &mut Buf, ymd: Ymd) -> fmt::Result {
       let part = Extractor::extract(ymd);
       write!(b, "{:01$}", part, W::WIDTH)
   }
}

impl<Extractor, W> FormatDate<Ymd> for Formatter<Extractor, W, NoPad>
where
   Extractor: Extract,
   W: Width,
{
   fn format_date(&self, b: &mut Buf, ymd: Ymd) -> fmt::Result {
       let part = Extractor::extract(ymd);
       write!(b, "{:1$}", part, W::WIDTH)
   }
}

Итак, мы приобрели в модульности, но что мы потеряли? Удобство использования! Каждый конкретный форматировщик теперь содержит поле, поэтому проинициализировать его просто по имени уже не получится. К счастью, это обходится достаточно просто: достаточно завести константы с нужными именами:

pub type Day = Formatter<DayOf, W2, PadZeros>;
pub const DAY: Day = Formatter(std::marker::PhantomData);
// Аналогично с остальными форматировщиками

Проверим:

let d = date!(2020-02-18);
let format = (DAY, "/", MONTH, "/", YEAR);
assert_eq!(d.format_as(format), "18/02/2020");

Работает!
источник
Блог*
Есть ещё один аспект, который мы можем улучшить: конструирование форматировщика из нескольких. Сейчас это сделано при помощи крайне уродливого и не расширяемого решения: реализации трейта для кортежей. Кортежи всей длины мы охватить не можем, а каждый новый impl добавляет времени к компиляции. Сейчас мы сделаем индуктивное решение: вместо того, чтобы пытаться объять необъятное, мы сделаем cons-список форматировщиков:

pub struct Nil;
pub struct Cons<T, U>(pub T, pub U);

impl FormatDate<Ymd> for Nil {
   fn format_date(&self, _: &mut Buf, _: Ymd) -> fmt::Result {
       Ok(())
   }
}

impl<T, U> FormatDate<Ymd> for Cons<T, U>
where
   T: FormatDate<Ymd>,
   U: FormatDate<Ymd>,
{
   fn format_date(&self, b: &mut Buf, ymd: Ymd) -> fmt::Result {
       self.0.format_date(b, ymd)?;
       self.1.format_date(b, ymd)?;
       Ok(())
   }
}

Разумеется, составлять подобный список руками куда менее удобно, чем кортеж — но мы и не будем! Вместо этого мы сделаем макрос, который будет конструировать список за нас. Мы несколько ужесточим требования к формату —  теперь вместо произвольных выражений можно использовать лишь имена и литералы — но это упростит видимый синтаксис, потому что позволяет избавиться от запятых. Сам макрос:

macro_rules! date_format {
   () => { Nil };
   ($name:ident $($tt:tt)*) => { Cons($name, date_format!($($tt)*)) };
   ($lit:literal $($tt:tt)*) => { Cons($lit, date_format!($($tt)*)) };
}

Как видите, ничего сложного тут нет 😈. Проверим, как это ведёт себя:

let d = date!(2020-02-18);
let format = date_format!(DAY" " MONTH_FULL", " YEAR);
assert_eq!(d.format_as(format), "18 February, 2020");

К сожалению, писать имена вплотную после литералов нельзя, потому что это синтаксическая ошибка. В остальном — оно работает!
источник
Блог*
Можем ли мы теперь объявить торжество zero-cost абстракций? К сожалению, нет: форматировщик, использующий Ymd, заставляет вызывать Date::as_ymd даже в том случае, если используется только одно из значений месяц или день — а переход на Ymd был совершён именно по соображениям производительности! У меня есть идеи, как можно решить этот недостаток, но... Это потребует несколько более тяжёлой ти́повой наркомании, так что это материал для следующей статьи.
источник