#enums #unsized #dst #trait-object

unsized_enum

未尺寸枚举实现

2个版本

0.0.2 2020年8月7日
0.0.1 2020年8月6日

#1830数据结构

MIT/Apache

24KB
399

未尺寸枚举

Rust不支持在枚举中使用未尺寸的(?Sized)变体。这个crate提供了一个包含一个未尺寸变体和一个尺寸变体的未尺寸枚举,与一个公共基结构一起返回装箱。枚举可以被读取和修改,包括切换变体,甚至可以通过特质对象引用。

文档

请参阅crate文档

许可证

此项目可在Apache许可证版本2或MIT许可证下使用,由您选择。(请参阅LICENSE-APACHELICENSE-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已经依赖于这种布局。

无运行时依赖