#prog #rust #моё
Хозяйке на заметку
Иногда бывает ситуация, что у вас на руках есть некое значение, про которое известно, что его тип реализует определённый трейт, и вам нужно вызвать метод этого трейта, но есть небольшая проблема: метод не принимает
Для определённости сделаем подобный трейт и напишем для него реализацию:
И напишем функцию, для результата вызова которой мы не можем использовать турборыбу:
Для того, чтобы решить нашу задачу, сделаем для
Теперь воспользуемся этим:
Использование fully qualified синтаксиса для вызова метода тут критично: так мы удостоверяемся, что мы действительно вызываем метод для нужного типа, а не для, скажем, ссылки на этот тип. Из-за deref coercion следующий код тоже работает:
Хозяйке на заметку
Иногда бывает ситуация, что у вас на руках есть некое значение, про которое известно, что его тип реализует определённый трейт, и вам нужно вызвать метод этого трейта, но есть небольшая проблема: метод не принимает
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);
Блог*
Можем ли мы теперь объявить торжество zero-cost абстракций? К сожалению, нет: форматировщик, использующий Ymd, заставляет вызывать Date::as_ymd даже в том случае, если используется только одно из значений месяц или день — а переход на Ymd был совершён именно…
Мне уже даже про Томаку шутить не хочется
Блог*
Мне уже даже про Томаку шутить не хочется
#prog #rust #моё
Самое смешное, что это в итоге так и не работает.
В текущем виде каждый форматировщик имеет имеет три ассоциированных типа (
Вся эта машинерия создавалась с расчётом на то, что из нескольких форматировщиков можно составить один. В этом случае провайдер создаётся лишь один раз и передаётся субформатировщикам, а требования к провайдеру являются объединением требований субформатировщиков. Именно с последним пунктом у меня возникли проблемы: rustc не понимал, что если провайдер удовлетворяет объединённым, более сильным условиям, то он автоматически удовлетворяет и более слабым требованиям субформатировщиков. Сейчас я понимаю, что мой метод объединения требований непрозрачный для компилятора и не сохраняет эту информацию. В общем, буду думать.
А, ну и, как вы можете видеть, даже если я решу эту задачу, решение, скорее всего, будет слишком сложным для применения на практике. В принципе, это не такая уж и большая потеря: обычно из даты форматируют все три главные компоненты, а в этом случае мой предыдущий метод не даёт дополнительного оверхеда.
Самое смешное, что это в итоге так и не работает.
В текущем виде каждый форматировщик имеет имеет три ассоциированных типа (
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
Если у вас есть варианты, пишите в личку мне или его адвокату @ArkChaplygin, пожалуйста.
Еще раз извиняюсь за оффтоп, запиню предыдущий пин обратно завтра
https://zona.media/news/2020/02/23/litreev
Медиазона
В Екатеринбурге по подозрению в покупке экстази задержали IT‑специалиста и создателя «Красной кнопки» Александра Литреева
В Екатеринбурге по подозрению в покупке экстази задержали основателя компании по кибербезопасности Vee Security Россия ...
Vlad Beskrovnyi
Очень извиняюсь за дикий оффтоп. Моего друга Александра Литреева задержали в Екатеринбурге. Вы очень поможете, если у вас есть квартира под сдачу в Екатеринбурге и вы можете оформить договор до 7 утра 24 февраля. Или у кого-то из ваших знакомых. Или если вы…
Немного контекста: https://zona.media/news/2020/02/23/litreev
Медиазона
В Екатеринбурге по подозрению в покупке экстази задержали IT‑специалиста и создателя «Красной кнопки» Александра Литреева
В Екатеринбурге по подозрению в покупке экстази задержали основателя компании по кибербезопасности Vee Security Россия ...
Блог*
Уже 150 подписчиков! 🎉 Хорошая скорость, однако
Уже 200! Прекрасно! 🎉 Спасибо, что читаете мой блог!
Блог*
Уже 200! Прекрасно! 🎉 Спасибо, что читаете мой блог!
This media is not supported in your browser
VIEW IN TELEGRAM
#prog #article #gamedev
Статья о том, как портировали на консоли игру, первоначально написанную на JavaScript. Спойлер: динамичность мешается.
habr.com/ru/post/489974/
Статья о том, как портировали на консоли игру, первоначально написанную на JavaScript. Спойлер: динамичность мешается.
habr.com/ru/post/489974/
Хабр
Kha vs HTML5: Компилируем JavaScript в C++
Предлагаю вашему вниманию перевод доклада Роберта Конрада с прошедшего в октябре прошлого года HaxeUp Sessions 2019 Linz. Данный доклад посвящен процессу портиро...
Блог*
#prog #article #gamedev Статья о том, как портировали на консоли игру, первоначально написанную на JavaScript. Спойлер: динамичность мешается. habr.com/ru/post/489974/
Фактически это пересказ вот этого выступления.
YouTube
Robert Konrad - Kha v HTML5
Slides for the talk: https://robdangero.us/haxeup_2019-10-25.pptx
#prog #rust #моё
Rust, будучи достаточно низкоуровневым языком, тем не менее, даёт возможность использовать замыкания. Эти замыкания не требуют динамической аллокации памяти и могут размещаться на стеке. Вкупе с оптимизатором LLVM это увеличивает шансы на то, что в сгенерированном коде замыкания в явном виде вообще не окажутся. Тем не менее, есть и обратная сторона медали: тип замыкания зависит от того, какие значение оно захватывает, поэтому в Rust каждый литерал замыкания имеет свой собственный тип. Например, код вида
Один из паттернов, который можно встретить в коде на Rust — это возврат того или иного замыкания в зависимости от некоторого условия. Для обхода ситуации с уникальными типами замыкания есть как минимум два пути:
а) Поместить замыкание в кучу и вернуть указатель на него, а в качестве возвращаемого типа указать что-то вроде
б) Вернуть замыкание, обёрнутое в один из вариантов некоторого перечисления. Здесь страдает уже эргономика: перечисление приходится разворачивать на стороне вызывающего кода, даже если все варианты имеют одинаковую сигнатуру. С именами также проблема: непонятно, как называть перечисление и его варианты, а различные
Сегодня я расскажу о том, как можно, переместив индирекцию внутрь самого замыкания, разобраться с некоторыми случаями этой проблемы. В качестве примеров я буду приводить код, который хотелось бы написать, но который не компилируется.
1) Одно и тоже выражение замыкания, но захват разных значений одного и того же типа в зависимости от некоторого условия. Пример:
3) Захват разных значений с разными операциями. Пример (спасибо, @psilon):
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)];
GitHub
rust/RELEASES.md at master · rust-lang/rust
Empowering everyone to build reliable and efficient software. - rust-lang/rust
#prog #rust #parsing #моё
При написании парсеров часто требуется отдельно отслеживать, когда мы находимся внутри строки, выделенной кавычками, а когда — вне её. На практике это выливается в то, что появляется цикл и отдельный флаг, который меняется в зависимости от рассматриваемого символа. Эта логика затёсывается где-то между остальными строками кода, внося лишнее состояние, мешая восприятию кода и повышая вероятность совершения ошибок при внесении изменений в код. Переделка цикла на итераторы/стримы — это известный способ повысить понятность кода, но в данном случае наличие внешнего состояния не даёт возможность прямолинейно провести эту трансформацию. К счастью, в Rust есть способ инкапсулировать это состояние так, чтобы оно не маячило перед глазами — и для этого нам даже не понадобится itertools. У трейта Iterator есть метод, который можно назвать "map с состоянием". Встречайте — Iterator::scan!
Вот как выглядит начало цепочки итераторов, которая отслеживает, находимся ли мы сейчас внутри строки или нет:
При написании парсеров часто требуется отдельно отслеживать, когда мы находимся внутри строки, выделенной кавычками, а когда — вне её. На практике это выливается в то, что появляется цикл и отдельный флаг, который меняется в зависимости от рассматриваемого символа. Эта логика затёсывается где-то между остальными строками кода, внося лишнее состояние, мешая восприятию кода и повышая вероятность совершения ошибок при внесении изменений в код. Переделка цикла на итераторы/стримы — это известный способ повысить понятность кода, но в данном случае наличие внешнего состояния не даёт возможность прямолинейно провести эту трансформацию. К счастью, в 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);
Как видите, весь код, относящийся непосредственно к отслеживанию строки, инкапсулирован в одном месте и не отвлекает на себя внимания.doc.rust-lang.org
Iterator in std::iter - Rust
A trait for dealing with iterators.
Закончили ли мы? Не совсем: в практичных языках нам также требуется возможность экранировать кавычки, чтобы включать кавычки в строки. Обычно это приводит к ещё большему зашумлению кода парсера. Что ж, реализуем поддержку этого требования. Для того, чтобы не слишком усложнять код, будем считать, что кавычки экранируются обратным слешем (\), а сам слеш — двойным повтором символа (\\). Код поменяется не очень сильно: нужно добавить ещё один 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 #rust
Semver trick, или как обновить библиотеку лишь частично.
https://github.com/dtolnay/semver-trick
Semver trick, или как обновить библиотеку лишь частично.
https://github.com/dtolnay/semver-trick
GitHub
GitHub - dtolnay/semver-trick: How to avoid complicated coordinated upgrades
How to avoid complicated coordinated upgrades. Contribute to dtolnay/semver-trick development by creating an account on GitHub.
#prog #python #amazingopensource
Библиотека, которая позволяет визуализировать частично пересекающиеся множества в достаточно наглядном виде. В отличие от традиционных диаграмм Венна, прекрасно масштабируется на большое количество множеств.
https://github.com/gecko984/supervenn
Библиотека, которая позволяет визуализировать частично пересекающиеся множества в достаточно наглядном виде. В отличие от традиционных диаграмм Венна, прекрасно масштабируется на большое количество множеств.
https://github.com/gecko984/supervenn
GitHub
GitHub - gecko984/supervenn: supervenn: precise and easy-to-read multiple sets visualization in Python
supervenn: precise and easy-to-read multiple sets visualization in Python - gecko984/supervenn
#prog
Шпаргалка по регулярным выражениям. Рисует синтаксические диаграммы, включает в себя интерактивную проверялку. Есть набор часто используемых шаблонов, в число которых, к сожалению, входит e-mail.
https://ihateregex.io
Шпаргалка по регулярным выражениям. Рисует синтаксические диаграммы, включает в себя интерактивную проверялку. Есть набор часто используемых шаблонов, в число которых, к сожалению, входит e-mail.
https://ihateregex.io
i Hate Regex
i Hate Regex - The Regex Cheat Sheet
i Hate Regex is a regex cheat sheet that also explains the commonly used expressions so that you understand it. Stop hating and start learning.
#prog #rust #rustlib
Библиотека для облегчения генерации кода на Rust. Ну, мало ли кого ностальгия по Go замучила.
https://github.com/carllerche/codegen
Библиотека для облегчения генерации кода на Rust. Ну, мало ли кого ностальгия по Go замучила.
https://github.com/carllerche/codegen
#gamedev
Пост о том, как упростили кэширование рендеринга ландшафта в FactorIO. Авторы действительно заботятся об игроках со слабыми компьютерами.
https://www.factorio.com/blog/post/fff-333
Пост о том, как упростили кэширование рендеринга ландшафта в FactorIO. Авторы действительно заботятся об игроках со слабыми компьютерами.
https://www.factorio.com/blog/post/fff-333
Factorio
Friday Facts #333 - Terrain scrolling | Factorio
Hello, We released 0.18.4 this week, same old same old, more bugfixes, more bugs, more changes. At this stage of development, not many interesting things are happening, we are just polishing what we have.