⬆️⬆️⬆️
А что нам, собственно говоря, нужно? Нам нужно доказательство того, что тип имеет нулевой размер. Доказательство чего-либо можно передать:
1) ограничением на тип (см.
2) фактом успешного вычисления на этапе компиляции константы (см. static_assert_size);
3) значением специального типа.
Действительно, если тип имеет приватные поля, то единственный способ сконструировать значение этого типа (за вычетом
⬇️⬇️⬇️
А что нам, собственно говоря, нужно? Нам нужно доказательство того, что тип имеет нулевой размер. Доказательство чего-либо можно передать:
1) ограничением на тип (см.
Send
и Sync
);2) фактом успешного вычисления на этапе компиляции константы (см. static_assert_size);
3) значением специального типа.
Действительно, если тип имеет приватные поля, то единственный способ сконструировать значение этого типа (за вычетом
unsafe
, разумеется) — это использовать публичную функцию, которая конструирует значение этого типа. Что ж, давайте так и поступим:pub mod zero_sized {И теперь будем явно требовать доказательство:
use std::marker::PhantomData;
// Доказательство должно быть параметризовано типом,
// чтобы доказательство для одного типа не могло быть использовано
// как доказательство другого типа.
pub struct ZeroSizeProof<T>(PhantomData<T>, ());
pub trait ZeroSized: Sized {
#[deny(const_err)]
const I_AM_ZERO_SIZED: ();
fn proof_zero_size(&self) -> ZeroSizeProof<Self>;
}
// опять-таки, blanket impl во избежание переопределения
impl<T: Sized> ZeroSized for T {
const I_AM_ZERO_SIZED: () = [()][std::mem::size_of::<Self>()];
fn proof_zero_size(&self) -> ZeroSizeProof<Self> {
ZeroSizeProof(PhantomData, Self::I_AM_ZERO_SIZED)
}
}
}
pub mod foo {Оно работает! Для удобства, чтобы пользователю не надо было каждый раз создавать доказательство руками, можно дописать extension trait:
use crate::zero_sized::*;
pub trait Foo: ZeroSized {
fn requires_zero_size(self, _proof: ZeroSizedProof<Self>) {
println!("requires_zero_size called");
}
}
}
pub mod foo {Проверим, как это работает:
use crate::zero_sized::*;
pub trait Foo: ZeroSized {
fn requires_zero_size(self, _proof: ZeroSizeProof<Self>) {
println!("requires_zero_size called");
}
}
pub trait FooExt: Foo {
fn requires_zero_size(self);
}
impl<T: Foo> FooExt for T {
fn requires_zero_size(self) {
let proof = self.proof_zero_size();
<Self as Foo>::requires_zero_size(self, proof)
}
}
}
use crate::zero_sized::ZeroSizeProof;Не компилируется! Как мы и хотели.
impl foo::Foo for () {}
impl foo::Foo for u32 {
fn requires_zero_size(self, _proof: ZeroSizeProof<Self>) {
println!("requires_zero_size was called, but Self is not zero-sized, bwa-ha-ha-ha!");
}
}
fn main() {
use foo::FooExt;
().requires_zero_size();
42_u32.requires_zero_size();
}
⬇️⬇️⬇️
GitHub
rust/macros.rs at a0312c156d8470179101ab71ef6a69c0b9a8dd0b · rust-lang/rust
Empowering everyone to build reliable and efficient software. - rust/macros.rs at a0312c156d8470179101ab71ef6a69c0b9a8dd0b · rust-lang/rust
⬆️⬆️⬆️
Что ж, теперь мы можем по праву гордиться тем, что изящно решили сложную проблему, не так ли?
⬇️⬇️⬇️
Что ж, теперь мы можем по праву гордиться тем, что изящно решили сложную проблему, не так ли?
⬇️⬇️⬇️
⬆️⬆️⬆️
На самом деле, это ужасно оверинжинирнутое решение.
Пользователь реализует один трейт, а в API используется другой.
Пользователь имеет дело с непонятным типом
Для системного языка, которым является Rust, это выглядит слишком сложным решением для простой задачи.
Но самое главное — наша желанная ошибка компиляции совершенно неинформативна:
Да, это решение работает. Но оно непрактично. Если не ненавидите всё человечество, просто вставьте у себя в нужном месте
Я всё.
На самом деле, это ужасно оверинжинирнутое решение.
Пользователь реализует один трейт, а в API используется другой.
Пользователь имеет дело с непонятным типом
ZeroSizedProof
.Для системного языка, которым является Rust, это выглядит слишком сложным решением для простой задачи.
Но самое главное — наша желанная ошибка компиляции совершенно неинформативна:
error: any use of this value will cause an errorКак видите, никакого указания на то, где именно ошибка на самом деле.
--> src/main.rs:13:38
|
13 | const I_AM_ZERO_SIZED: () = [()][std::mem::size_of::<Self>()];
| -----------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
| |
| index out of bounds: the len is 1 but the index is 4
|
= note: `#[deny(const_err)]` on by default
Да, это решение работает. Но оно непрактично. Если не ненавидите всё человечество, просто вставьте у себя в нужном месте
assert_eq!(std::mem::size_of::<Self>(), 0);
и задокументируете это, компилятор это вырежет как мёртвый код.Я всё.
Блог*
#prog #rust Допустим, ты пишешь на Rust библиотеку и определяешь трейт, для вызова метода которого по каким-то причинам требуется, чтобы Self был ZST. Для удобства дальнейшего изложения сделаем подобное определение: pub mod foo { pub trait Foo { …
Компилируется на nightly, когда не должно? Это ошибка. Кстати, на бете почему-то тоже компилируется.
GitHub
Usage of errorneous constant can be omitted on nightly and beta · Issue #67083 · rust-lang/rust
Consider the following code: trait ZeroSized: Sized { #[deny(const_err)] const I_AM_ZERO_SIZED: (); fn requires_zero_size(self); } impl<T: Sized> ZeroSized for T { const I_AM_ZERO_SIZ...
Мы, конечно, знаем, что программисты умнее обычных людей, но это не заставляет их автоматически правильно говорить на английском.
За наводку спасибо @lilfunctor.
youtube.com/watch?v=jXJFuqHnF2Q
За наводку спасибо @lilfunctor.
youtube.com/watch?v=jXJFuqHnF2Q
YouTube
Произношение некоторых английских слов
Предыдущее видео о транслитерации в навигации:
https://www.youtube.com/watch?v=SjjgP-8S70E
3:55 Произношение некоторых английских слов
Упоминаю слова: apple, archive, content, done, none, height, weight, width, variable, image, engine, defer, choir, tier…
https://www.youtube.com/watch?v=SjjgP-8S70E
3:55 Произношение некоторых английских слов
Упоминаю слова: apple, archive, content, done, none, height, weight, width, variable, image, engine, defer, choir, tier…
Forwarded from Oleg Andreev