万一有人看呢?
首先,明确堆上储存了什么
毫无疑问,堆上的数据无疑就是一个长度为 N
的 i32
数组
然后,Box<[i32; N]>
是怎么储存的
依次将结构体展开,并将指针 *const T
用编译器实际行为 core::ptr::metadata::PtrComponents<T>
替代
(资料图)
非零大小而具有实际内容的数据紧密排列后,最终布局为
此时,对于任意的 N
故称指向 [i32; N]
的指针是瘦指针,[i32; N]
实现了特性 Thin = Pointee<Metadata = ()>
那么,Box<[i32]>
是怎么储存的
同样进行展开,但是指针的元数据类型发生了改变
紧密排列后的最终布局为
此时
故称指向 [i32]
的指针是胖指针,[i32]
实现了特性 Pointee<Metadata = usize>
转换过程中发生了什么
很显然,在 *const [i32; N] as *const [i32]
的过程中,从编译时具有的类型信息中抹去了 N
,就必须在运行时具有的内存数据中放下 N
那么,从 Box<i32>
到 Box<dyn Any>
呢
堆上的数据非常简单
而 Box<i32>
则为
排列毫无疑问
那么 Box<dyn Any>
呢
排列则增加了虚表
此时
因此指向 dyn Any
的指针也是胖指针,dyn Any
实现了特性 Pointee<Metadata = DynMetadata<dyn Any>>
那么,虚表是什么
虚表对于用户来说是外部的类型,虽然我们不知道外部类型的大小,extern_type
没有实现 Sized
,但指向外部类型的指针依然是瘦的,extern_type
实现了 Thin
最后一个问题,虚表有什么
很显然,在 *const i32 as *const dyn Any
的过程中,从编译时具有的类型信息中抹去了实际数据类型布局和虚方法表,就必须在运行时具有的内存数据中放下它们
数据类型的布局,包括对齐和大小
虚方法表则包括特性及其父特性要求的所有方法,以及可能具有的最为特殊的 Drop::drop
方法实现
一些其他内容
Thin
和 Sized
的关系是什么
Thin
是 Sized
和 extern_type
的总和,实践中具有 Sized: Thin
,Sized
比 Thin
更严格
如何获取 DynMetadata<Dyn>
的布局
DynMetadata<Dyn>::layout(self) -> Layout
获取布局,布局由对齐和大小两个部分组成
DynMetadata<Dyn>::size_of(self) -> usize
获取大小
DynMetadata<Dyn>::align_of(self) -> usize
获取对齐
如何在泛型中表示像 [i32; N]
与 [i32]
、i32
与 dyn Any
之间的关系
使用 T: Unsize<Dyn>
其中 core::marker::Usize<T: ?Sized>
具有此关系后,可以在他们的指针上使用 as
如何在泛型中表示像 Box<[i32; N]>
与 Box<[i32]>
、Box<i32>
与 Box<dyn Any>
之间的关系
使用 BoxT: CoerceUnsized<BoxDyn>
其中 core::ops::unsize::CoerceUnsized<T: ?Sized>
具有此关系后,可以在他们上使用 as
其他的其他
本文提及的一些特性还未稳定,如果你在代码中使用,需要 nightly 版本编译器并声明需要的功能
#![feature(unsize)]
允许 trait Unsize
的使用
#![feature(coerce_unsized)]
允许 trait CoerceUnsized
的使用
#![feature(ptr_metadata)]
允许指针元数据相关API的使用
#![feature(extern_types)]
允许外部类型的使用
本文在未使用分配器的情况下讨论,如果使用分配器API,情况可能发生改变