Rust 安全代码
安全与非安全代码对比 安全与非安全代码交互 非安全Rust能做什么 编写非安全代码数据布局
repr(Rust) 类型中的奇行种 可选的数据表达方式所有权
所有权 引用 别名 生命周期 生命周期的局限 省略生命周期 无界生命周期 高阶trait边界 子类型和变性 Drop检查 幽灵数据 分解借用类型转换
类型转换 强制类型转换 点操作符 显式类型转换 变形未初始化内存
未初始化内存 安全方式 Drop标志 非安全方式资源管理
基于所有权的资源管理 构造函数 析构函数 泄露展开
展开 异常安全性 污染并发
并发 竞争 Send和Sync 原子操作实现 Vec
实现 Vec 布局 内存分配 push和pop 回收资源 DeRef 插入和删除 IntoIter RawVec Drain 处理零尺寸类型 最终代码FFI
FFIRust 安全代码
安全与非安全代码对比 安全与非安全代码交互 非安全Rust能做什么 编写非安全代码数据布局
repr(Rust) 类型中的奇行种 可选的数据表达方式所有权
所有权 引用 别名 生命周期 生命周期的局限 省略生命周期 无界生命周期 高阶trait边界 子类型和变性 Drop检查 幽灵数据 分解借用类型转换
类型转换 强制类型转换 点操作符 显式类型转换 变形未初始化内存
未初始化内存 安全方式 Drop标志 非安全方式资源管理
基于所有权的资源管理 构造函数 析构函数 泄露展开
展开 异常安全性 污染并发
并发 竞争 Send和Sync 原子操作实现 Vec
实现 Vec 布局 内存分配 push和pop 回收资源 DeRef 插入和删除 IntoIter RawVec Drain 处理零尺寸类型 最终代码FFI
FFIRust 可选的数据表达方式
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)不总是内存管理的最佳方案,在一些场景中 ...
AI 中文社