Rust 可选的数据表达方式
Rust允许你选择其他的数据布局策略。
1. repr(C)
这是最重要的一种repr
。它的目的很简单,就是和C保持一致。数据的顺序、大小、对齐方式都和你在C或C++中见到的一摸一样。所有你需要通过FFI交互的类型都应该有repr(C)
,因为C是程序设计领域的世界语。而且如果我们要在数据布局方面玩一些花活的话,比如把数据重新解析成另一种类型,repr(C)
也是很有必要的。
但是,一定不要忘了Rust的那几个奇行种。repr(C)
的存在有双重作用,既为了FFI同时也为了常规的布局控制,所以它可以被应用于那些在FFI中没有意义甚至会产生错误的类型。
- 尽管标准的C语言不支持大小为0的类型,但ZST的尺寸仍然是0。而且它也与C++中的空类型有着明显的不同,C++的空类型还是要占用一个字节的空间的。
- DST的指针(胖指针),元组,和带有成员变量的枚举都是C中没有的,因此也不是FFI安全的。
- 如果
T
是一个FFI安全的非空指针,那么Option<T>
可以保证和T
拥有相同的布局和ABI,当然它也会是FFI安全的。这一规则适用于&
,&mut
和函数指针等所有非空的指针。 - 在
repr(C)
中元组结构体与结构体基本相同,唯一的不同是其成员都是未命名的。 - 对于枚举的处理和
repr(u*)
是相同的(见下一节)。选择的类型尺寸等于目标平台上C的应用二进制接口(ABI)的默认枚举尺寸。注意C中枚举的数据布局是确定的,所以这确实是一种“最合理的假设”。不过,当目标C代码编译时加了一些特殊的编译器参数时,这一点可能就不正确了。 repr(C)
和repr(u*)
中无成员的枚举不能被赋值为一个没有对应变量的整数,尽管在C\C++中这是一种合法的行为。构建一个没有对应变量的枚举类型实例属于未定义行为。(对于存在准确匹配的值是允许正常编写和编译的)
2. repr(u), repr(i)
这两个可以指定无成员枚举的大小。如果枚举变量对应的整数值对于设定的大小越界了,将产生一个编译期错误。你可以手工设置越界的元素为0以避免编译错误,不过要注意Rust是不允许一个枚举中的两个变量拥有相同的值的。
“无成员枚举”的意思是枚举的每一个变量里都不关联数据。不指定repr(u*)
或repr(i*)
的无成员枚举依然是一个Rust的合法原生类型,它们都没有固定的ABI表示方法。给它们指定repr
使其有了固定的类型大小,方便在ABI中使用。
Rust中所有有成员的枚举都没有确定的ABI表示方式(即使关联的数据只是PhantomData
或者零尺寸类型的数据)。
为枚举显式指定repr
后空指针优化将不再起作用。
这些repr
对于结构体无效。
3. repr(packed)
repr(packed)
强制Rust不填充空数据,各个类型的数据紧密排列。这样有助于提升内存的使用效率,但很可能会导致其他的副作用。
尤其是大部分平台都强烈建议数据对齐。这意味着加载未对齐的数据会很低效(x86),甚至是错误的(一些ARM芯片)。像直接加载或存储打包的(packed)成员变量这种简单的场景,编译器可能可以用shift和mask等方式隐藏对齐问题。但是如果是使用一个打包的变量的引用,编译器很可能没办法避免未对齐加载问题。
在Rust 1.0中这会导致未定义行为。
repr(packed)
不应该随便使用。只有在你有一些极端的需求的情况下才该用它。
这个repr是repr(C)
和repr(Rust)
的修饰器。
下一章:Rust 所有权
所有权是Rust的一个突破性功能。它让Rust可以彻底告别垃圾回收,同时做到内存安全和高效率。在涉及到所有权系统的细节之前,我们先看一下这种设计的目的。我们假设你认同垃圾回收器(GC)不总是内存管理的最佳方案,在一些场景中 ...