

Size: a a a
Self
был ZST. Для удобства дальнейшего изложения сделаем подобное определение:pub mod foo {В идеале для этого достаточно было бы навесить на
pub trait Foo {
fn requires_zero_size(self) {
println!("requires_zero_size called");
}
}
}
Self
ограничение : ZeroSized
, который является auto-трейтом, но... Такого трейта в std
нет.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 {Теперь мы натыкаемся на ошибку E0401. Окей, если константа не сработает, может, просто возьмём значение?
fn requires_zero_size(self) {
const _: () = Self::I_AM_ZERO_SIZED;
println!("requires_zero_size called");
}
}
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 Foo for u32 {Конечно, мы могли бы написать blanket impl, но тогда пользователи трейта не смогли бы (по крайней мере, без специализации) использовать собственную реализацию Foo. (Есть ещё и третья проблема: даже без кастомного impl-a этот код каким-то образом компилируется на nightly, но с этим я точно ничего сделать не могу).
fn requires_zero_size(self) {
println!("requires_zero_size was called, but Self is not zero-sized, bwa-ha-ha-ha!");
}
}
#[macro_export]Умный трюк, ничего не скажешь: std::mem::transmute проверяет, что типы имеют одинаковый размер, и отключить это поведение никак нельзя. К сожалению, попытка использовать этот макрос (как и его код) непосредственно приведёт к уже знакомой нам ошибке E0401, но можем адаптировать этот подход для своего трейта:
macro_rules! assert_eq_size {
($x:ty, $($xs:ty),+ $(,)?) => {
const _: fn() = || {
$(let _ = $crate::_core::mem::transmute::<$x, $xs>;)+
};
};
}
pub mod zero_sized {Посмотрим, что скажет компилятор:
pub trait ZeroSized: Sized {
#[deny(const_err)]
const I_AM_ZERO_SIZED: unsafe extern "rust-intrinsic" fn(Self);
}
impl<T: Sized> ZeroSized for T {
const I_AM_ZERO_SIZED: unsafe extern "rust-intrinsic" fn(Self) = std::mem::transmute::<Self, ()>;
}
}
error[E0658]: intrinsics are subject to change...Ну, или не можем. Макросу достаточно просто получить значение, а нам нужен ещё и тип.
--> src/main.rs:4:32
|
4 | const I_AM_ZERO_SIZED: unsafe extern "rust-intrinsic" fn(Self);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0658]: intrinsics are subject to change
--> src/main.rs:10:32
|
10 | const I_AM_ZERO_SIZED: unsafe extern "rust-intrinsic" fn(Self) = std::mem::transmute::<Self, ()>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0308]: cannot coerce intrinsics to function pointers
--> src/main.rs:10:74
|
10 | const I_AM_ZERO_SIZED: unsafe extern "rust-intrinsic" fn(Self) = std::mem::transmute::<Self, ()>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot coerce intrinsics to function pointers
|
= note: expected type `unsafe extern "rust-intrinsic" fn(T)`
found type `unsafe extern "rust-intrinsic" fn(T) {std::intrinsics::transmute::<T, ()>}`
pub mod zero_sized {Что ж:
pub trait ZeroSized: Sized {
#[deny(const_err)]
const I_AM_ZERO_SIZED: ();
}
impl<T: Sized> ZeroSized for T {
const I_AM_ZERO_SIZED: () = (std::mem::transmute::<Self, ()>, ()).1;
}
}
error[E0512]: cannot transmute between types of different sizes, or dependently-sized typesТеперь мы получаем ошибку компиляции... Всегда. Даже без impl-ов.
--> src/main.rs:10:38
|
10 | const I_AM_ZERO_SIZED: () = (std::mem::transmute::<Self, ()>, ()).1;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: source type: `T` (this type does not have a fixed size)
= note: target type: `()` (0 bits)
Send
и Sync
);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();
}
ZeroSizedProof
.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);
и задокументируете это, компилятор это вырежет как мёртвый код.Self
был ZST. Для удобства дальнейшего изложения сделаем подобное определение:pub mod foo {В идеале для этого достаточно было бы навесить на
pub trait Foo {
fn requires_zero_size(self) {
println!("requires_zero_size called");
}
}
}
Self
ограничение : ZeroSized
, который является auto-трейтом, но... Такого трейта в std
нет.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 {Теперь мы натыкаемся на ошибку E0401. Окей, если константа не сработает, может, просто возьмём значение?
fn requires_zero_size(self) {
const _: () = Self::I_AM_ZERO_SIZED;
println!("requires_zero_size called");
}
}
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 Foo for u32 {Конечно, мы могли бы написать blanket impl, но тогда пользователи трейта не смогли бы (по крайней мере, без специализации) использовать собственную реализацию Foo. (Есть ещё и третья проблема: даже без кастомного impl-a этот код каким-то образом компилируется на nightly, но с этим я точно ничего сделать не могу).
fn requires_zero_size(self) {
println!("requires_zero_size was called, but Self is not zero-sized, bwa-ha-ha-ha!");
}
}