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

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

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
Channel created
Channel photo updated
😁3
#prog #rust
Допустим, ты пишешь на Rust библиотеку и определяешь трейт, для вызова метода которого по каким-то причинам требуется, чтобы Self был ZST. Для удобства дальнейшего изложения сделаем подобное определение:
pub mod foo {
pub trait Foo {
fn requires_zero_size(self) {
println!("requires_zero_size called");
}
}
}
В идеале для этого достаточно было бы навесить на Self ограничение : ZeroSized, который является auto-трейтом, но... Такого трейта в std нет.

Окей, наученный опытом static_assertions, ты пишешь примерно следующее:
pub mod zero_sized {
pub trait ZeroSized: Sized {
#[deny(const_err)] //потому что выше по скоупу может быть #[allow(const_err)]
const I_AM_ZERO_SIZED: ();
}

// blanket impl вместо дефолтного значения, чтобы I_AM_ZERO_SIZED нельзя было переопределить
impl<T: Sized> ZeroSized for T {
const I_AM_ZERO_SIZED: () = [()][std::mem::size_of::<Self>()]; //является ошибкой, если Self имеет ненулевой размер
}
}

pub mod foo {
pub trait Foo: super::zero_sized::ZeroSized {
fn requires_zero_size(self) {
println!("requires_zero_size called");
}
}
}
Проверим, как оно работает:
use foo::Foo;

impl Foo for () {}
impl Foo for u32 {}
И оно... Компилируется.

То есть ошибка при вычислении константы трейта не возникает, если эту константу не использовать. В принципе, логично — зная определение трейта, компилятор не может наперёд сказать, будет ли константа вычислена для всех типов корректно. Что ж, поменяем определение Foo:
    pub trait Foo: super::zero_sized::ZeroSized {
fn requires_zero_size(self) {
const _: () = Self::I_AM_ZERO_SIZED;
println!("requires_zero_size called");
}
}
Теперь мы натыкаемся на ошибку E0401. Окей, если константа не сработает, может, просто возьмём значение?
    pub trait Foo: super::zero_sized::ZeroSized {
fn requires_zero_size(self) {
let () = Self::I_AM_ZERO_SIZED;
println!("requires_zero_size called");
}
}
И теперь... Ошибки компиляции нет. Что, давайте действительно вызовем этот метод:
fn main() {
().requires_zero_size();
42_u32.requires_zero_size();
}
Вот теперь ошибка компиляции есть.

Значит ли это, что мы решили проблему? Ничего подобного. Во-первых, ошибка компиляции возникает при использовании метода, а не при определении impl-а. Во-вторых — и это куда как более серьёзная проблема — этот подход опирается на реализацию метода по умолчанию, которую можно переопределить:
impl Foo for u32 {
fn requires_zero_size(self) {
println!("requires_zero_size was called, but Self is not zero-sized, bwa-ha-ha-ha!");
}
}
Конечно, мы могли бы написать blanket impl, но тогда пользователи трейта не смогли бы (по крайней мере, без специализации) использовать собственную реализацию Foo. (Есть ещё и третья проблема: даже без кастомного impl-a этот код каким-то образом компилируется на nightly, но с этим я точно ничего сделать не могу).
⬇️⬇️⬇️