2个版本
0.0.2 | 2020年8月7日 |
---|---|
0.0.1 | 2020年8月6日 |
#1830 在 数据结构
24KB
399 行
未尺寸枚举
Rust不支持在枚举中使用未尺寸的(?Sized
)变体。这个crate提供了一个包含一个未尺寸变体和一个尺寸变体的未尺寸枚举,与一个公共基结构一起返回装箱。枚举可以被读取和修改,包括切换变体,甚至可以通过特质对象引用。
文档
请参阅crate文档。
许可证
此项目可在Apache许可证版本2或MIT许可证下使用,由您选择。(请参阅LICENSE-APACHE和LICENSE-MIT)。
贡献
除非您明确声明,否则任何有意提交以包含在此crate中并由您定义的贡献,根据Apache-2.0许可证,应以上述双重许可,没有任何额外条款或条件。
lib.rs
:
Rust未尺寸枚举实现
截至2020年中期稳定Rust中,?Sized
(即未尺寸或特质对象或“动态尺寸类型”或DST)支持在多个地方缺失
-
Rust的内置枚举不支持
?Sized
变体 -
Option
不支持?Sized
(因为它是一个枚举) -
Rust的联合类型不支持
?Sized
-
MaybeUninit
也不支持?Sized
(因为它目前建立在联合之上)
因此,如果枚举中需要未尺寸类型,必须采取其他方法。目前这个crate实现了一个枚举(UnsizedEnum
),包含一个未尺寸变体和一个尺寸变体,总是返回装箱。(这种方法只能支持一个单个未尺寸变体,尽管它可以扩展以提供额外的尺寸变体。)
枚举可以被读取和修改,包括切换变体,甚至可以通过特质对象引用。
安全和稳健性讨论
该包旨在保证安全性,如果能够证明其不安全性,将会修复(如果可能)或者标记API为不安全,直到找到安全的解决方案。然而,目前该包的理论(而非实际)安全性取决于Rust安全合同尚未确定的部分。因此,如果您对此感到不安,请暂时不要使用此包。
使用两个结构体访问boxed内存,这两个结构体表示对该内存的两种视图:使用UnsizedEnum
表示头部加上V0
(非固定大小)变体,以及使用UnsizedEnum_V1
表示头部加上V1
(固定大小)变体。使用#[repr(C)]
来强制成员的顺序,并确保UnsizedEnum
结构体中的头部部分与UnsizedEnum_V1
兼容。
必须将V0
实例直接包含在UnsizedEnum
结构体中,因为其清理必须通过vtable处理。如果没有在UnsizedEnum
中包含V0
值,似乎Drop处理程序没有收到胖指针,因此无法访问vtable。然而,在存储V1
变体的情况下,包含在UnsizedEnum
中的V0
值不能被丢弃,因为这将是V0
类型的无效数据。因此,将V0
值设置为ManuallyDrop
,这样我们就可以在V1
变体情况下跳过丢弃无效的V0
数据。(MaybeUninit
会更好,但它还不支持?Sized
。)
因此,严格来说,在存储V1
变体的情况下,因为UnsizedEnum
结构体包含val: ManuallyDrop<V0>
,我们正在处理无效的UnsizedEnum
引用(在val
部分无效)。但我们从未“生成”无效的UnsizedEnum
值。V1
值是使用UnsizedEnum_V1
生成的。唯一公开给整个无效UnsizedEnum
的代码是编译器生成的Drop代码。(传递无效数据的引用在理论上是否安全尚未确定,但似乎共识倾向于它是安全的。)
填充特定领域优化的过程中,不应尝试使用V0
值中的任何未使用位模式来存储数据,因为这些可能会覆盖V1
变体中的值。然而,由于这个实现完全控制结构,并且结构是以装箱形式返回的,所以库用户无法将ManuallyDrop<V0>
值包裹在一个enum
中,因此不应存在填充尝试使用ManuallyDrop
内部内存的情况。因此,编译器生成的析构代码不应该有理由触及V0
变体的内存,在V1
的情况下。因此,我们的析构实现可以自由地跳过丢弃(无效的)V0
值,并丢弃覆盖的V1
值。
如果Rust在更多地方支持?Sized
,特别是MaybeUninit
,此实现将得到改进,以使用这些功能。这也会解决关于持有无效数据引用的理论合理性的问题。
此外,还需要在set_v0
中比较vtable指针。这取决于胖指针的布局。这风险较低,因为如果编译器中的布局发生变化,测试将立即非常明显地失败。此外,许多其他crate已经依赖于这种布局。