1.84K subscribers
3.27K photos
130 videos
15 files
3.55K links
Блог со звёздочкой.

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

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

Well-Typed Programs Can Go Wrong: A Study of Typing-Related Bugs in JVM Compilers

В этой статье авторы отмечают, что усилия в тестировании компиляторов в основном направлены на отлов некорректных оптимизаций, при этом мало кто целенаправленно занимается отловом багов в фронтендах. Авторы выбрали 320 багов, связанных с типами, среди багов компиляторов Java, Scala, Kotlin и Groovy, и разобрались с тем, как они себя проявляют, какие фичи языка используют и как они фиксятся. Вооружившись этим знанием, они написали генератор тестовых программ, который смог найти 28 багов, из них 12 были новыми (то есть для них исправлений не было).

А ещё авторы отличились тем, что нормально выложили материалы по исследованию.
#prog #rust #article

Небольшая заметка о различиях между ящиками крейтами и пакетами в Cargo/crates.io
#prog #rust #article

Stack-safety for free?

TL;DR:

//рекурсивная функция, использующая системный стек
fn triangular(n: u64) -> u64 {
if n == 0 {
0
} else {
n + triangular(n - 1)
}
}

⬇️

//использует стек на куче
fn triangular_safe(n: u64) -> u64 {
trampoline(|n| move |_| {
if n == 0 {
0
} else {
n + yield (n - 1)
}
})(n)
}

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

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

Также автор отдельно отмечает, что у него было чувство, что он изобрёл велосипед, но он не смог найти prior art для этого.
Если вы когда-нибудь почувствуете себя глупым, то вспомните, что я разок опоздал на встречу с человеком из-за того, что перепутал станции Октябрьская и Октябрьское поле.
#rust

9 companies using Rust in production.

О некоторых из них я уже рассказывал, но в любом случае иметь сборник лишним не будет.
Понятия не имею, насколько правда, но звучит смешно
простите
#meme

Сайт, кстати, реально есть. Но там ничего интересного
#prog #rust

Синтаксис для условной компиляции (#[cfg(...)]) в Rust позволяет использовать условные выражения, но не в очень эргономичном виде: через all, any и not. Библиотека efg позволяет использовать более привычный синтаксис, похожий на то, что пишут в коде на самом Rust. Пример:

#[efg(feature "proc-macro" && !(target_arch "wasm32" && target_os "unknown"))]
extern crate proc_macro;

Кстати, написана Толяном.
#prog #rust #article

How not to learn Rust — статья с частыми ошибками новичков при изучении Rust
#prog #rust #article

Очередная статья про обработку ошибок в Rust, да. Но полезная: ставит под сомнения и разбирает распространённые мнения об ошибках в Rust. В частности, разбирается совет "anyhow для приложений, thiserror для библиотек".
#prog #rust #моё

Пара слов о фантомных типах и авто-трейтах.

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

struct OptionA;
struct OptionB;

trait Param {
fn op();
}

impl Param for OptionA { ... }
impl Param for OptionB { ... }

struct Parametrised<P> {
data: Data,
_param: PhantomData<P>,
}

impl<P: Param> Parametrised<P> {
fn func(&mut self) {
self.data.process_foo();
P::op();
self.data.process_bar();
}
}

Вроде выглядит нормально. Однако тут есть подвох: авто-трейты, в отличие от дерайвов для большинства трейтов, смотрят не на типовые параметры, а на типы самих полей. В число авто-трейтов входят также Send и Sync. PhantomData (не) имеет те же реализации Send/Sync, что и тИповый параметр, которым параметризован. К чему это приводит? Да к тому, что если Parametrised<P> будет использован в контексте, когда выполнение этих трейтов имеет значение, нам придётся вешать ограничение P: Send/P: Sync, даже не смотря на то, что значения этих типов не используются вообще. Хуже, это придётся писать даже в том случае, если Data реализует Send/Sync. Что мы можем с этим сделать?

Ответ очень прост: достаточно поменять определение поля _param:

struct Parametrised<P> {
data: Data,
_param: PhantomData<fn(P)>, // <--
}

Таким образом, мы всё ещё включаем тип P в описание типа Parametrised, но так как все функциональные указателя безусловно удовлетворяют Send и Sync, паразитные ограничения более не возникают.

Есть, однако, и другой вариант использования фантомных типовых параметров: мы пишем тип данных, который как-то соотносится с неким параметром T, но не включает его напрямую. Использование PhantomData<T> может быть неправильным в том смысле, что оно показывает, что наш тип логически владеет значением типа T, что не обязательно является правдой. Более того, так как из-за лайфтаймов на типах в Rust есть отношение субтипизации, в полный рост встают проблемы вариантности.

Прямолинейным подходом в подобных случаях является использование PhantomData, параметризованных *const T и *mut T. Этот подход опирается на тот факт, что *const T ковариантен по T, а *mut T контравариантен по T, и оба типа не требуют лайфтаймы. Однако тут легко упустить тот момент, что оба типа примитивных указателей безусловно не реализуют ни Send, ни Sync, что вкупе с протеканием автотрейтов через PhantomData может привести к тем же паразитным ограничениям. Причём этот случай это даже хуже, поскольку требует написания реализаций Send и Sync вручную, а эти имплы очень легко могут стать неправильными при внесении изменений в код.

Для того, чтобы избежать этих проблем и иметь ограничения Send/Sync только на релевантные поля, мы опять воспользуемся тем фактом, что функциональные указатели реализуют Send и Sync безусловно, а так же тем фактом, что функциональные типы ковариантны по типу результата и контрвариантны по типу аргумента. Комбинируя эти типы с PhantomData, мы можем получить нужную нам вариантность без головной боли, связанной с потокобезопасностью. Проиллюстрирую это псевдонимами типов:

type Covariant<T> = PhantomData<fn() -> T>;
type Contravariant<T> = PhantomData<fn(T)>;
// ко- и контрвариантность — это про наложение ограничений,
// поэтому совмещение этих требований приводит к
// более строгому отношению: инвариантности (в смысле вариантности)
type Invariant<T> = PhantomData<fn(T) -> T>;