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

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

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
А когда я вырасту, я стану Томакой?

#typed_phy
Важный вопрос: прикручивать ли лайки?
(особенно с учётом того, что у канала вроде как есть чат)
Final Results
7%
Да
26%
Да, но только на свои посты
52%
Нет
14%
Кешбери
Блог* pinned «Важный вопрос: прикручивать ли лайки?
(особенно с учётом того, что у канала вроде как есть чат)
»
Forwarded from The After Times
Когда твоя программа в целом работает не так, как планировал, но свои функции выполняет
#meme
Я, конечно, понимаю, что это успело обойти все тематические каналы в телеге, но это всё-таки смешно
Forwarded from The After Times
#article #math #prog

Как вы считаете середину отрезка в числах с плавающей точкой? Скорее всего, неправильно. Разбор различных методов сделать это, вместе с доказательствами на ошибки вычислений. И да, 80-битные регистры в x86 опять всё портят.

https://hal.archives-ouvertes.fr/file/index/docid/576641/filename/computing-midpoint.pdf
#prog #rust #моё

Хозяйке на заметку

Иногда бывает ситуация, что у вас на руках есть некое значение, про которое известно, что его тип реализует определённый трейт, и вам нужно вызвать метод этого трейта, но есть небольшая проблема: метод не принимает Self (как, например, Default::default), а воспользоваться синтаксисом турборыбы нельзя, потому что тип не имеет имени, которое можно написать (скажем, если вы получили это значение из функции, возвращающей impl Trait). Как же поступить в подобном случае? Сделать метод на значении самому!

Для определённости сделаем подобный трейт и напишем для него реализацию:

trait WithTypeMethod {
fn method(arg: i32);
}

impl WithTypeMethod for () {
fn method(arg: i32) {
println!("Called `<() as WithTypeMethod>::method` with arg = {}", arg)
}
}


И напишем функцию, для результата вызова которой мы не можем использовать турборыбу:

fn make_it() -> impl WithTypeMethod + Default {
()
}


Для того, чтобы решить нашу задачу, сделаем для WithTypeMethod extension trait с методом, принимающим &self, который просто вызывает Type::method с нужным аргументом:

trait LowerToInstance: WithTypeMethod {
fn instance_method(&self, arg: i32);
}

impl<T: WithTypeMethod> LowerToInstance for T {
fn instance_method(&self, arg: i32) {
T::method(arg)
}
}


Теперь воспользуемся этим:

let it = make_it();
LowerToInstance::instance_method(&it, 42);


Использование fully qualified синтаксиса для вызова метода тут критично: так мы удостоверяемся, что мы действительно вызываем метод для нужного типа, а не для, скажем, ссылки на этот тип. Из-за deref coercion следующий код тоже работает:

let it = make_it();
(&&&&&&&it).instance_method(18);
Блог*
Мне уже даже про Томаку шутить не хочется
#prog #rust #моё

Самое смешное, что это в итоге так и не работает.

В текущем виде каждый форматировщик имеет имеет три ассоциированных типа (Year, Month и Day), которые описывают, нужна ли этому форматировщику эта часть даты или нет — фактически, этими типами могут быть только Reqired и Optional. Главный метод для форматирования принимает на вход сам форматировщик, буфер и некий обобщённый провайдер, про который известно только то, что он даёт доступ к полям, нужным для форматировщика. В типах это выражается тем, что это обобщённый тип, удовлетворяющий трейту Satisfies<Self::Year, Self::Month, Self::Day>. Второй метод принимает на вход форматировщик, буфер и дату и имеют реализацию по умолчанию, которая создаёт провайдер данных из даты (именно в этот момент применяется оптимизация с вызовом month_day вместо month и day по отдельности) и вызывает на форматировщике первый метод.

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

А, ну и, как вы можете видеть, даже если я решу эту задачу, решение, скорее всего, будет слишком сложным для применения на практике. В принципе, это не такая уж и большая потеря: обычно из даты форматируют все три главные компоненты, а в этом случае мой предыдущий метод не даёт дополнительного оверхеда.
Forwarded from Vlad Beskrovnyi
Очень извиняюсь за дикий оффтоп. Моего друга Александра Литреева задержали в Екатеринбурге. Вы очень поможете, если у вас есть квартира под сдачу в Екатеринбурге и вы можете оформить договор до 7 утра 24 февраля. Или у кого-то из ваших знакомых. Или если вы можете кинуть это в другие чаты. Если до 7 утра не найдем, то отъедет в СИЗО почти наверняка.

Если у вас есть варианты, пишите в личку мне или его адвокату @ArkChaplygin, пожалуйста.
Еще раз извиняюсь за оффтоп, запиню предыдущий пин обратно завтра
https://zona.media/news/2020/02/23/litreev
Forwarded from Антон
Народ, приведите, пожалуйста, ситуации, когда вам нужно было в Rust вернуть замыкание, но этого не получалось сделать прямолинейно из-за того, что каждое замыкание имеет свой тип (и приходилось боксить/оборачивать в Either/что-то ещё)
Блог*
Уже 150 подписчиков! 🎉 Хорошая скорость, однако
Уже 200! Прекрасно! 🎉 Спасибо, что читаете мой блог!
#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